├── .babelrc ├── .circleci └── config.yml ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .idea ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── dash-color-picker.iml ├── inspectionProfiles │ └── Project_Default.xml ├── misc.xml ├── modules.xml └── vcs.xml ├── .npmignore ├── .prettierrc ├── .pylintrc ├── CHANGELOG.md ├── MANIFEST.in ├── README.md ├── dash_dual_listbox-0.0.1.tar.gz ├── dash_dual_listbox.egg-info ├── PKG-INFO ├── SOURCES.txt ├── dependency_links.txt └── top_level.txt ├── dash_dual_listbox ├── DualList.py ├── __init__.py ├── _imports_.py ├── bundle.js ├── metadata.json ├── package.json └── style.css ├── extract-meta ├── index.html ├── package-lock.json ├── package.json ├── setup.py ├── src ├── demo │ ├── App.js │ ├── index.js │ └── style.css └── lib │ ├── components │ └── DualList.react.js │ └── index.js ├── tests ├── IntegrationTests.py ├── __init__.py ├── requirements.txt └── test_render.py ├── usage.py ├── webpack.config.js └── webpack.serve.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env", "react"] 3 | } 4 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | jobs: 4 | "node": 5 | docker: 6 | - image: circleci/node:8.11.3 7 | 8 | steps: 9 | - checkout 10 | 11 | - restore_cache: 12 | key: deps1-{{ .Branch }}-{{ checksum "package.json" }} 13 | 14 | - run: 15 | name: Install package.json 16 | command: npm i 17 | 18 | - save_cache: 19 | key: deps1-{{ .Branch }}-{{ checksum "package.json" }} 20 | paths: 21 | - node_modules 22 | 23 | - run: 24 | name: Run eslint 25 | command: ./node_modules/.bin/eslint src 26 | when: always 27 | 28 | 29 | "python-3.6": 30 | docker: 31 | - image: circleci/python:3.6-stretch-browsers 32 | 33 | environment: 34 | PERCY_ENABLED: False 35 | 36 | steps: 37 | - checkout 38 | 39 | - restore_cache: 40 | key: deps1-{{ .Branch }}-{{ checksum "tests/requirements.txt" }} 41 | 42 | - run: 43 | name: Create virtualenv 44 | command: | 45 | python3 -m venv venv 46 | 47 | - run: 48 | name: Install requirements 49 | command: | 50 | . venv/bin/activate 51 | pip install -r tests/requirements.txt --quiet 52 | 53 | - save_cache: 54 | key: deps1-{{ .Branch }}-{{ checksum "tests/requirements.txt" }} 55 | paths: 56 | - "venv" 57 | 58 | - run: 59 | name: Run pylint 60 | command: | 61 | . venv/bin/activate 62 | pylint usage.py tests 63 | when: always 64 | 65 | - run: 66 | name: Run flake8 67 | command: | 68 | . venv/bin/activate 69 | flake8 usage.py tests 70 | when: always 71 | 72 | - run: 73 | name: Integration Tests 74 | command: | 75 | . venv/bin/activate 76 | python -m unittest tests.test_render 77 | when: always 78 | 79 | 80 | workflows: 81 | version: 2 82 | build: 83 | jobs: 84 | - "python-3.6" 85 | - "node" 86 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | *.css 2 | registerServiceWorker.js -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["eslint:recommended", "prettier"], 3 | "parser": "babel-eslint", 4 | "parserOptions": { 5 | "ecmaVersion": 6, 6 | "sourceType": "module", 7 | "ecmaFeatures": { 8 | "arrowFunctions": true, 9 | "blockBindings": true, 10 | "classes": true, 11 | "defaultParams": true, 12 | "destructuring": true, 13 | "forOf": true, 14 | "generators": true, 15 | "modules": true, 16 | "templateStrings": true, 17 | "jsx": true 18 | } 19 | }, 20 | "env": { 21 | "browser": true, 22 | "es6": true, 23 | "jasmine": true, 24 | "jest": true, 25 | "node": true 26 | }, 27 | "globals": { 28 | "jest": true 29 | }, 30 | "plugins": [ 31 | "react", 32 | "import" 33 | ], 34 | "rules": { 35 | "accessor-pairs": ["error"], 36 | "block-scoped-var": ["error"], 37 | "consistent-return": ["error"], 38 | "curly": ["error", "all"], 39 | "default-case": ["error"], 40 | "dot-location": ["off"], 41 | "dot-notation": ["error"], 42 | "eqeqeq": ["error"], 43 | "guard-for-in": ["off"], 44 | "import/named": ["off"], 45 | "import/no-duplicates": ["error"], 46 | "import/no-named-as-default": ["error"], 47 | "new-cap": ["error"], 48 | "no-alert": [1], 49 | "no-caller": ["error"], 50 | "no-case-declarations": ["error"], 51 | "no-console": ["off"], 52 | "no-div-regex": ["error"], 53 | "no-dupe-keys": ["error"], 54 | "no-else-return": ["error"], 55 | "no-empty-pattern": ["error"], 56 | "no-eq-null": ["error"], 57 | "no-eval": ["error"], 58 | "no-extend-native": ["error"], 59 | "no-extra-bind": ["error"], 60 | "no-extra-boolean-cast": ["error"], 61 | "no-inline-comments": ["error"], 62 | "no-implicit-coercion": ["error"], 63 | "no-implied-eval": ["error"], 64 | "no-inner-declarations": ["off"], 65 | "no-invalid-this": ["error"], 66 | "no-iterator": ["error"], 67 | "no-labels": ["error"], 68 | "no-lone-blocks": ["error"], 69 | "no-loop-func": ["error"], 70 | "no-multi-str": ["error"], 71 | "no-native-reassign": ["error"], 72 | "no-new": ["error"], 73 | "no-new-func": ["error"], 74 | "no-new-wrappers": ["error"], 75 | "no-param-reassign": ["error"], 76 | "no-process-env": ["warn"], 77 | "no-proto": ["error"], 78 | "no-redeclare": ["error"], 79 | "no-return-assign": ["error"], 80 | "no-script-url": ["error"], 81 | "no-self-compare": ["error"], 82 | "no-sequences": ["error"], 83 | "no-shadow": ["off"], 84 | "no-throw-literal": ["error"], 85 | "no-undefined": ["error"], 86 | "no-unused-expressions": ["error"], 87 | "no-use-before-define": ["error", "nofunc"], 88 | "no-useless-call": ["error"], 89 | "no-useless-concat": ["error"], 90 | "no-with": ["error"], 91 | "prefer-const": ["error"], 92 | "radix": ["error"], 93 | "react/jsx-no-duplicate-props": ["error"], 94 | "react/jsx-no-undef": ["error"], 95 | "react/jsx-uses-react": ["error"], 96 | "react/jsx-uses-vars": ["error"], 97 | "react/no-did-update-set-state": ["error"], 98 | "react/no-direct-mutation-state": ["error"], 99 | "react/no-is-mounted": ["error"], 100 | "react/no-unknown-property": ["error"], 101 | "react/prefer-es6-class": ["error", "always"], 102 | "react/prop-types": "error", 103 | "valid-jsdoc": ["off"], 104 | "yoda": ["error"], 105 | "spaced-comment": ["error", "always", { 106 | "block": { 107 | exceptions: ["*"] 108 | } 109 | }], 110 | "no-unused-vars": ["error", { 111 | "args": "after-used", 112 | "argsIgnorePattern": "^_", 113 | "caughtErrorsIgnorePattern": "^e$" 114 | }], 115 | "no-magic-numbers": ["error", { 116 | "ignoreArrayIndexes": true, 117 | "ignore": [-1, 0, 1, 2, 3, 100, 10, 0.5] 118 | }], 119 | "no-underscore-dangle": ["off"] 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # production 7 | /build 8 | /demo 9 | 10 | # testing 11 | /coverage 12 | 13 | # misc 14 | .DS_Store 15 | .env.local 16 | .env.development.local 17 | .env.test.local 18 | .env.production.local 19 | 20 | npm-debug.log* 21 | yarn-debug.log* 22 | yarn-error.log* 23 | 24 | # virtualenv 25 | vv 26 | venv 27 | 28 | # python 29 | *.pyc 30 | 31 | # builds 32 | my_dash_component.egg-info 33 | dist 34 | *__pycache__* 35 | __pycache__/ 36 | 37 | *.pyc 38 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/dash-color-picker.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 12 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 10 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | /node_modules 3 | 4 | # testing 5 | /coverage 6 | 7 | # misc 8 | .DS_Store 9 | .env.local 10 | .env.development.local 11 | .env.test.local 12 | .env.production.local 13 | 14 | npm-debug.log* 15 | yarn-debug.log* 16 | yarn-error.log* 17 | 18 | # Development folders and files 19 | public 20 | src 21 | scripts 22 | config 23 | .travis.yml 24 | CHANGELOG.md 25 | README.md 26 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 4, 3 | "singleQuote": true, 4 | "bracketSpacing": false, 5 | "trailingComma": "es5" 6 | } 7 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | 3 | # A comma-separated list of package or module names from where C extensions may 4 | # be loaded. Extensions are loading into the active Python interpreter and may 5 | # run arbitrary code 6 | extension-pkg-whitelist= 7 | 8 | # Add files or directories to the blacklist. They should be base names, not 9 | # paths. 10 | ignore=CVS 11 | 12 | # Add files or directories matching the regex patterns to the blacklist. The 13 | # regex matches against base names, not paths. 14 | ignore-patterns= 15 | 16 | # Python code to execute, usually for sys.path manipulation such as 17 | # pygtk.require(). 18 | #init-hook= 19 | 20 | # Use multiple processes to speed up Pylint. 21 | jobs=1 22 | 23 | # List of plugins (as comma separated values of python modules names) to load, 24 | # usually to register additional checkers. 25 | load-plugins= 26 | 27 | # Pickle collected data for later comparisons. 28 | persistent=yes 29 | 30 | # Specify a configuration file. 31 | #rcfile= 32 | 33 | # When enabled, pylint would attempt to guess common misconfiguration and emit 34 | # user-friendly hints instead of false-positive error messages 35 | suggestion-mode=yes 36 | 37 | # Allow loading of arbitrary C extensions. Extensions are imported into the 38 | # active Python interpreter and may run arbitrary code. 39 | unsafe-load-any-extension=no 40 | 41 | 42 | [MESSAGES CONTROL] 43 | 44 | # Only show warnings with the listed confidence levels. Leave empty to show 45 | # all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED 46 | confidence= 47 | 48 | # Disable the message, report, category or checker with the given id(s). You 49 | # can either give multiple identifiers separated by comma (,) or put this 50 | # option multiple times (only on the command line, not in the configuration 51 | # file where it should appear only once).You can also use "--disable=all" to 52 | # disable everything first and then reenable specific checks. For example, if 53 | # you want to run only the similarities checker, you can use "--disable=all 54 | # --enable=similarities". If you want to run only the classes checker, but have 55 | # no Warning level messages displayed, use"--disable=all --enable=classes 56 | # --disable=W" 57 | disable=print-statement, 58 | parameter-unpacking, 59 | unpacking-in-except, 60 | old-raise-syntax, 61 | backtick, 62 | long-suffix, 63 | old-ne-operator, 64 | old-octal-literal, 65 | import-star-module-level, 66 | non-ascii-bytes-literal, 67 | raw-checker-failed, 68 | bad-inline-option, 69 | locally-disabled, 70 | locally-enabled, 71 | file-ignored, 72 | suppressed-message, 73 | useless-suppression, 74 | deprecated-pragma, 75 | apply-builtin, 76 | basestring-builtin, 77 | buffer-builtin, 78 | cmp-builtin, 79 | coerce-builtin, 80 | execfile-builtin, 81 | file-builtin, 82 | long-builtin, 83 | raw_input-builtin, 84 | reduce-builtin, 85 | standarderror-builtin, 86 | unicode-builtin, 87 | xrange-builtin, 88 | coerce-method, 89 | delslice-method, 90 | getslice-method, 91 | setslice-method, 92 | no-absolute-import, 93 | old-division, 94 | dict-iter-method, 95 | dict-view-method, 96 | next-method-called, 97 | metaclass-assignment, 98 | indexing-exception, 99 | raising-string, 100 | reload-builtin, 101 | oct-method, 102 | hex-method, 103 | nonzero-method, 104 | cmp-method, 105 | input-builtin, 106 | round-builtin, 107 | intern-builtin, 108 | unichr-builtin, 109 | map-builtin-not-iterating, 110 | zip-builtin-not-iterating, 111 | range-builtin-not-iterating, 112 | filter-builtin-not-iterating, 113 | using-cmp-argument, 114 | eq-without-hash, 115 | div-method, 116 | idiv-method, 117 | rdiv-method, 118 | exception-message-attribute, 119 | invalid-str-codec, 120 | sys-max-int, 121 | bad-python3-import, 122 | deprecated-string-function, 123 | deprecated-str-translate-call, 124 | deprecated-itertools-function, 125 | deprecated-types-field, 126 | next-method-defined, 127 | dict-items-not-iterating, 128 | dict-keys-not-iterating, 129 | dict-values-not-iterating, 130 | no-member, 131 | missing-docstring, 132 | invalid-name, 133 | redefined-builtin, 134 | wrong-import-order, 135 | too-many-arguments, 136 | too-many-locals, 137 | consider-using-enumerate, 138 | len-as-condition, 139 | too-many-branches, 140 | too-many-statements, 141 | blacklisted-name, 142 | line-too-long, 143 | bare-except, 144 | duplicate-code, 145 | too-many-function-args, 146 | attribute-defined-outside-init, 147 | broad-except 148 | 149 | # Enable the message, report, category or checker with the given id(s). You can 150 | # either give multiple identifier separated by comma (,) or put this option 151 | # multiple time (only on the command line, not in the configuration file where 152 | # it should appear only once). See also the "--disable" option for examples. 153 | enable=c-extension-no-member 154 | 155 | 156 | [REPORTS] 157 | 158 | # Python expression which should return a note less than 10 (10 is the highest 159 | # note). You have access to the variables errors warning, statement which 160 | # respectively contain the number of errors / warnings messages and the total 161 | # number of statements analyzed. This is used by the global evaluation report 162 | # (RP0004). 163 | evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) 164 | 165 | # Template used to display messages. This is a python new-style format string 166 | # used to format the message information. See doc for all details 167 | #msg-template= 168 | 169 | # Set the output format. Available formats are text, parseable, colorized, json 170 | # and msvs (visual studio).You can also give a reporter class, eg 171 | # mypackage.mymodule.MyReporterClass. 172 | output-format=text 173 | 174 | # Tells whether to display a full report or only the messages 175 | reports=no 176 | 177 | # Activate the evaluation score. 178 | score=yes 179 | 180 | 181 | [REFACTORING] 182 | 183 | # Maximum number of nested blocks for function / method body 184 | max-nested-blocks=5 185 | 186 | # Complete name of functions that never returns. When checking for 187 | # inconsistent-return-statements if a never returning function is called then 188 | # it will be considered as an explicit return statement and no message will be 189 | # printed. 190 | never-returning-functions=optparse.Values,sys.exit 191 | 192 | 193 | [BASIC] 194 | 195 | # Naming style matching correct argument names 196 | argument-naming-style=snake_case 197 | 198 | # Regular expression matching correct argument names. Overrides argument- 199 | # naming-style 200 | #argument-rgx= 201 | 202 | # Naming style matching correct attribute names 203 | attr-naming-style=snake_case 204 | 205 | # Regular expression matching correct attribute names. Overrides attr-naming- 206 | # style 207 | #attr-rgx= 208 | 209 | # Bad variable names which should always be refused, separated by a comma 210 | bad-names=foo, 211 | bar, 212 | baz, 213 | toto, 214 | tutu, 215 | tata 216 | 217 | # Naming style matching correct class attribute names 218 | class-attribute-naming-style=any 219 | 220 | # Regular expression matching correct class attribute names. Overrides class- 221 | # attribute-naming-style 222 | #class-attribute-rgx= 223 | 224 | # Naming style matching correct class names 225 | class-naming-style=PascalCase 226 | 227 | # Regular expression matching correct class names. Overrides class-naming-style 228 | #class-rgx= 229 | 230 | # Naming style matching correct constant names 231 | const-naming-style=UPPER_CASE 232 | 233 | # Regular expression matching correct constant names. Overrides const-naming- 234 | # style 235 | #const-rgx= 236 | 237 | # Minimum line length for functions/classes that require docstrings, shorter 238 | # ones are exempt. 239 | docstring-min-length=-1 240 | 241 | # Naming style matching correct function names 242 | function-naming-style=snake_case 243 | 244 | # Regular expression matching correct function names. Overrides function- 245 | # naming-style 246 | #function-rgx= 247 | 248 | # Good variable names which should always be accepted, separated by a comma 249 | good-names=i, 250 | j, 251 | k, 252 | ex, 253 | Run, 254 | _ 255 | 256 | # Include a hint for the correct naming format with invalid-name 257 | include-naming-hint=no 258 | 259 | # Naming style matching correct inline iteration names 260 | inlinevar-naming-style=any 261 | 262 | # Regular expression matching correct inline iteration names. Overrides 263 | # inlinevar-naming-style 264 | #inlinevar-rgx= 265 | 266 | # Naming style matching correct method names 267 | method-naming-style=snake_case 268 | 269 | # Regular expression matching correct method names. Overrides method-naming- 270 | # style 271 | #method-rgx= 272 | 273 | # Naming style matching correct module names 274 | module-naming-style=snake_case 275 | 276 | # Regular expression matching correct module names. Overrides module-naming- 277 | # style 278 | #module-rgx= 279 | 280 | # Colon-delimited sets of names that determine each other's naming style when 281 | # the name regexes allow several styles. 282 | name-group= 283 | 284 | # Regular expression which should only match function or class names that do 285 | # not require a docstring. 286 | no-docstring-rgx=^_ 287 | 288 | # List of decorators that produce properties, such as abc.abstractproperty. Add 289 | # to this list to register other decorators that produce valid properties. 290 | property-classes=abc.abstractproperty 291 | 292 | # Naming style matching correct variable names 293 | variable-naming-style=snake_case 294 | 295 | # Regular expression matching correct variable names. Overrides variable- 296 | # naming-style 297 | #variable-rgx= 298 | 299 | 300 | [FORMAT] 301 | 302 | # Expected format of line ending, e.g. empty (any line ending), LF or CRLF. 303 | expected-line-ending-format= 304 | 305 | # Regexp for a line that is allowed to be longer than the limit. 306 | ignore-long-lines=^\s*(# )??$ 307 | 308 | # Number of spaces of indent required inside a hanging or continued line. 309 | indent-after-paren=4 310 | 311 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 312 | # tab). 313 | indent-string=' ' 314 | 315 | # Maximum number of characters on a single line. 316 | max-line-length=100 317 | 318 | # Maximum number of lines in a module 319 | max-module-lines=1000 320 | 321 | # List of optional constructs for which whitespace checking is disabled. `dict- 322 | # separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. 323 | # `trailing-comma` allows a space between comma and closing bracket: (a, ). 324 | # `empty-line` allows space-only lines. 325 | no-space-check=trailing-comma, 326 | dict-separator 327 | 328 | # Allow the body of a class to be on the same line as the declaration if body 329 | # contains single statement. 330 | single-line-class-stmt=no 331 | 332 | # Allow the body of an if to be on the same line as the test if there is no 333 | # else. 334 | single-line-if-stmt=no 335 | 336 | 337 | [LOGGING] 338 | 339 | # Logging modules to check that the string format arguments are in logging 340 | # function parameter format 341 | logging-modules=logging 342 | 343 | 344 | [MISCELLANEOUS] 345 | 346 | # List of note tags to take in consideration, separated by a comma. 347 | notes=FIXME, 348 | XXX, 349 | 350 | 351 | [SIMILARITIES] 352 | 353 | # Ignore comments when computing similarities. 354 | ignore-comments=yes 355 | 356 | # Ignore docstrings when computing similarities. 357 | ignore-docstrings=yes 358 | 359 | # Ignore imports when computing similarities. 360 | ignore-imports=no 361 | 362 | # Minimum lines number of a similarity. 363 | min-similarity-lines=4 364 | 365 | 366 | [SPELLING] 367 | 368 | # Limits count of emitted suggestions for spelling mistakes 369 | max-spelling-suggestions=4 370 | 371 | # Spelling dictionary name. Available dictionaries: none. To make it working 372 | # install python-enchant package. 373 | spelling-dict= 374 | 375 | # List of comma separated words that should not be checked. 376 | spelling-ignore-words= 377 | 378 | # A path to a file that contains private dictionary; one word per line. 379 | spelling-private-dict-file= 380 | 381 | # Tells whether to store unknown words to indicated private dictionary in 382 | # --spelling-private-dict-file option instead of raising a message. 383 | spelling-store-unknown-words=no 384 | 385 | 386 | [TYPECHECK] 387 | 388 | # List of decorators that produce context managers, such as 389 | # contextlib.contextmanager. Add to this list to register other decorators that 390 | # produce valid context managers. 391 | contextmanager-decorators=contextlib.contextmanager 392 | 393 | # List of members which are set dynamically and missed by pylint inference 394 | # system, and so shouldn't trigger E1101 when accessed. Python regular 395 | # expressions are accepted. 396 | generated-members= 397 | 398 | # Tells whether missing members accessed in mixin class should be ignored. A 399 | # mixin class is detected if its name ends with "mixin" (case insensitive). 400 | ignore-mixin-members=yes 401 | 402 | # This flag controls whether pylint should warn about no-member and similar 403 | # checks whenever an opaque object is returned when inferring. The inference 404 | # can return multiple potential results while evaluating a Python object, but 405 | # some branches might not be evaluated, which results in partial inference. In 406 | # that case, it might be useful to still emit no-member and other checks for 407 | # the rest of the inferred objects. 408 | ignore-on-opaque-inference=yes 409 | 410 | # List of class names for which member attributes should not be checked (useful 411 | # for classes with dynamically set attributes). This supports the use of 412 | # qualified names. 413 | ignored-classes=optparse.Values,thread._local,_thread._local 414 | 415 | # List of module names for which member attributes should not be checked 416 | # (useful for modules/projects where namespaces are manipulated during runtime 417 | # and thus existing member attributes cannot be deduced by static analysis. It 418 | # supports qualified module names, as well as Unix pattern matching. 419 | ignored-modules= 420 | 421 | # Show a hint with possible names when a member name was not found. The aspect 422 | # of finding the hint is based on edit distance. 423 | missing-member-hint=yes 424 | 425 | # The minimum edit distance a name should have in order to be considered a 426 | # similar match for a missing member name. 427 | missing-member-hint-distance=1 428 | 429 | # The total number of similar names that should be taken in consideration when 430 | # showing a hint for a missing member. 431 | missing-member-max-choices=1 432 | 433 | 434 | [VARIABLES] 435 | 436 | # List of additional names supposed to be defined in builtins. Remember that 437 | # you should avoid to define new builtins when possible. 438 | additional-builtins= 439 | 440 | # Tells whether unused global variables should be treated as a violation. 441 | allow-global-unused-variables=yes 442 | 443 | # List of strings which can identify a callback function by name. A callback 444 | # name must start or end with one of those strings. 445 | callbacks=cb_, 446 | _cb 447 | 448 | # A regular expression matching the name of dummy variables (i.e. expectedly 449 | # not used). 450 | dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ 451 | 452 | # Argument names that match this expression will be ignored. Default to name 453 | # with leading underscore 454 | ignored-argument-names=_.*|^ignored_|^unused_ 455 | 456 | # Tells whether we should check for unused import in __init__ files. 457 | init-import=no 458 | 459 | # List of qualified module names which can have objects that can redefine 460 | # builtins. 461 | redefining-builtins-modules=six.moves,past.builtins,future.builtins 462 | 463 | 464 | [CLASSES] 465 | 466 | # List of method names used to declare (i.e. assign) instance attributes. 467 | defining-attr-methods=__init__, 468 | __new__, 469 | setUp 470 | 471 | # List of member names, which should be excluded from the protected access 472 | # warning. 473 | exclude-protected=_asdict, 474 | _fields, 475 | _replace, 476 | _source, 477 | _make 478 | 479 | # List of valid names for the first argument in a class method. 480 | valid-classmethod-first-arg=cls 481 | 482 | # List of valid names for the first argument in a metaclass class method. 483 | valid-metaclass-classmethod-first-arg=mcs 484 | 485 | 486 | [DESIGN] 487 | 488 | # Maximum number of arguments for function / method 489 | max-args=5 490 | 491 | # Maximum number of attributes for a class (see R0902). 492 | max-attributes=7 493 | 494 | # Maximum number of boolean expressions in a if statement 495 | max-bool-expr=5 496 | 497 | # Maximum number of branch for function / method body 498 | max-branches=12 499 | 500 | # Maximum number of locals for function / method body 501 | max-locals=15 502 | 503 | # Maximum number of parents for a class (see R0901). 504 | max-parents=7 505 | 506 | # Maximum number of public methods for a class (see R0904). 507 | max-public-methods=20 508 | 509 | # Maximum number of return / yield for function / method body 510 | max-returns=6 511 | 512 | # Maximum number of statements in function / method body 513 | max-statements=50 514 | 515 | # Minimum number of public methods for a class (see R0903). 516 | min-public-methods=2 517 | 518 | 519 | [IMPORTS] 520 | 521 | # Allow wildcard imports from modules that define __all__. 522 | allow-wildcard-with-all=no 523 | 524 | # Analyse import fallback blocks. This can be used to support both Python 2 and 525 | # 3 compatible code, which means that the block might have code that exists 526 | # only in one or another interpreter, leading to false positives when analysed. 527 | analyse-fallback-blocks=no 528 | 529 | # Deprecated modules which should not be used, separated by a comma 530 | deprecated-modules=optparse,tkinter.tix 531 | 532 | # Create a graph of external dependencies in the given file (report RP0402 must 533 | # not be disabled) 534 | ext-import-graph= 535 | 536 | # Create a graph of every (i.e. internal and external) dependencies in the 537 | # given file (report RP0402 must not be disabled) 538 | import-graph= 539 | 540 | # Create a graph of internal dependencies in the given file (report RP0402 must 541 | # not be disabled) 542 | int-import-graph= 543 | 544 | # Force import order to recognize a module as part of the standard 545 | # compatibility libraries. 546 | known-standard-library= 547 | 548 | # Force import order to recognize a module as part of a third party library. 549 | known-third-party=enchant 550 | 551 | 552 | [EXCEPTIONS] 553 | 554 | # Exceptions that will emit a warning when being caught. Defaults to 555 | # "Exception" 556 | overgeneral-exceptions=Exception 557 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include dash_dual_listbox/bundle.js 2 | include dash_dual_listbox/metadata.json 3 | include dash_dual_listbox/package.json 4 | include dash_dual_listbox/style.css 5 | include README.md 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Dual listbox for Dash. Original component: https://rawgit.com/jyotirmaybanerjee/react-duallist/master/example/examples.html# 2 | 3 | Install using: 4 | pip install dash-dual-listbox 5 | -------------------------------------------------------------------------------- /dash_dual_listbox-0.0.1.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vivekvs1/dash-dual-listbox/9067f3d93a022c00155bc68b7bdc55c7ab530097/dash_dual_listbox-0.0.1.tar.gz -------------------------------------------------------------------------------- /dash_dual_listbox.egg-info/PKG-INFO: -------------------------------------------------------------------------------- 1 | Metadata-Version: 1.0 2 | Name: dash-dual-listbox 3 | Version: 0.0.1 4 | Summary: Dual listbox for Dash. Original component: https://rawgit.com/jyotirmaybanerjee/react-duallist/master/example/examples.html# 5 | Home-page: UNKNOWN 6 | Author: Vivek Shankar vivekvs1@gmail.com 7 | Author-email: UNKNOWN 8 | License: MIT 9 | Description: UNKNOWN 10 | Platform: UNKNOWN 11 | -------------------------------------------------------------------------------- /dash_dual_listbox.egg-info/SOURCES.txt: -------------------------------------------------------------------------------- 1 | MANIFEST.in 2 | README.md 3 | setup.py 4 | dash_dual_listbox/DualList.py 5 | dash_dual_listbox/__init__.py 6 | dash_dual_listbox/_imports_.py 7 | dash_dual_listbox/bundle.js 8 | dash_dual_listbox/metadata.json 9 | dash_dual_listbox/package.json 10 | dash_dual_listbox/style.css 11 | dash_dual_listbox.egg-info/PKG-INFO 12 | dash_dual_listbox.egg-info/SOURCES.txt 13 | dash_dual_listbox.egg-info/dependency_links.txt 14 | dash_dual_listbox.egg-info/top_level.txt -------------------------------------------------------------------------------- /dash_dual_listbox.egg-info/dependency_links.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /dash_dual_listbox.egg-info/top_level.txt: -------------------------------------------------------------------------------- 1 | dash_dual_listbox 2 | -------------------------------------------------------------------------------- /dash_dual_listbox/DualList.py: -------------------------------------------------------------------------------- 1 | # AUTO GENERATED FILE - DO NOT EDIT 2 | 3 | from dash.development.base_component import Component, _explicitize_args 4 | 5 | 6 | class DualList(Component): 7 | """A DualList component. 8 | 9 | 10 | Keyword arguments: 11 | - id (string; optional): The ID of this component, used to identify dash components 12 | in callbacks. The ID needs to be unique across all of the 13 | components in an app. 14 | - selected (list; required): List of selected options, will appear in the right box 15 | - available (list; required): List of available options, will appear in the left box 16 | - leftLabel (string; optional): A header for the left (available) list 17 | - rightLabel (string; optional): A header for the right (selected) list 18 | - searchable (boolean; optional): A false value will hide the search field on the top 19 | - sortable (boolean; optional): A false value will hide the reorder buttons on the right 20 | - moveLeftIcon (string; optional): fontawesome icons or icon of your choice 21 | - moveRightIcon (string; optional): fontawesome icons or icon of your choice 22 | - moveAllLeftIcon (string; optional): fontawesome icons or icon of your choice 23 | - moveAllRightIcon (string; optional): fontawesome icons or icon of your choice 24 | - moveUpIcon (string; optional): fontawesome icons or icon of your choice 25 | - moveTopIcon (string; optional): fontawesome icons or icon of your choice 26 | - moveDownIcon (string; optional): fontawesome icons or icon of your choice 27 | - moveBottomIcon (string; optional): fontawesome icons or icon of your choice 28 | 29 | Available events: """ 30 | @_explicitize_args 31 | def __init__(self, id=Component.UNDEFINED, selected=Component.REQUIRED, available=Component.REQUIRED, leftLabel=Component.UNDEFINED, rightLabel=Component.UNDEFINED, searchable=Component.UNDEFINED, sortable=Component.UNDEFINED, moveLeftIcon=Component.UNDEFINED, moveRightIcon=Component.UNDEFINED, moveAllLeftIcon=Component.UNDEFINED, moveAllRightIcon=Component.UNDEFINED, moveUpIcon=Component.UNDEFINED, moveTopIcon=Component.UNDEFINED, moveDownIcon=Component.UNDEFINED, moveBottomIcon=Component.UNDEFINED, **kwargs): 32 | self._prop_names = ['id', 'selected', 'available', 'leftLabel', 'rightLabel', 'searchable', 'sortable', 'moveLeftIcon', 'moveRightIcon', 'moveAllLeftIcon', 'moveAllRightIcon', 'moveUpIcon', 'moveTopIcon', 'moveDownIcon', 'moveBottomIcon'] 33 | self._type = 'DualList' 34 | self._namespace = 'dash_dual_listbox' 35 | self._valid_wildcard_attributes = [] 36 | self.available_events = [] 37 | self.available_properties = ['id', 'selected', 'available', 'leftLabel', 'rightLabel', 'searchable', 'sortable', 'moveLeftIcon', 'moveRightIcon', 'moveAllLeftIcon', 'moveAllRightIcon', 'moveUpIcon', 'moveTopIcon', 'moveDownIcon', 'moveBottomIcon'] 38 | self.available_wildcard_properties = [] 39 | 40 | _explicit_args = kwargs.pop('_explicit_args') 41 | _locals = locals() 42 | _locals.update(kwargs) # For wildcard attrs 43 | args = {k: _locals[k] for k in _explicit_args if k != 'children'} 44 | 45 | for k in ['selected', 'available']: 46 | if k not in args: 47 | raise TypeError( 48 | 'Required argument `' + k + '` was not specified.') 49 | super(DualList, self).__init__(**args) 50 | 51 | def __repr__(self): 52 | if(any(getattr(self, c, None) is not None 53 | for c in self._prop_names 54 | if c is not self._prop_names[0]) 55 | or any(getattr(self, c, None) is not None 56 | for c in self.__dict__.keys() 57 | if any(c.startswith(wc_attr) 58 | for wc_attr in self._valid_wildcard_attributes))): 59 | props_string = ', '.join([c+'='+repr(getattr(self, c, None)) 60 | for c in self._prop_names 61 | if getattr(self, c, None) is not None]) 62 | wilds_string = ', '.join([c+'='+repr(getattr(self, c, None)) 63 | for c in self.__dict__.keys() 64 | if any([c.startswith(wc_attr) 65 | for wc_attr in 66 | self._valid_wildcard_attributes])]) 67 | return ('DualList(' + props_string + 68 | (', ' + wilds_string if wilds_string != '' else '') + ')') 69 | else: 70 | return ( 71 | 'DualList(' + 72 | repr(getattr(self, self._prop_names[0], None)) + ')') 73 | -------------------------------------------------------------------------------- /dash_dual_listbox/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function as _ 2 | 3 | import os as _os 4 | import sys as _sys 5 | import json 6 | 7 | import dash as _dash 8 | 9 | if not hasattr(_dash, 'development'): 10 | print('Dash was not successfully imported. ' 11 | 'Make sure you don\'t have a file ' 12 | 'named \n"dash.py" in your current directory.', file=_sys.stderr) 13 | _sys.exit(1) 14 | 15 | _basepath = _os.path.dirname(__file__) 16 | _filepath = _os.path.abspath(_os.path.join(_basepath, 'package.json')) 17 | with open(_filepath) as f: 18 | package = json.load(f) 19 | 20 | package_name = package['name'].replace(' ', '_').replace('-', '_') 21 | __version__ = package['version'] 22 | 23 | _current_path = _os.path.dirname(_os.path.abspath(__file__)) 24 | _components = _dash.development.component_loader.load_components( 25 | _os.path.join(_current_path, 'metadata.json'), 26 | package_name 27 | ) 28 | 29 | _this_module = _sys.modules[__name__] 30 | 31 | 32 | _js_dist = [ 33 | { 34 | 'relative_package_path': 'bundle.js', 35 | 'external_url': ( 36 | 'https://unpkg.com/my_dash_component' 37 | '/' + package_name + '/bundle.js' 38 | ).format(__version__), 39 | 'namespace': package_name 40 | } 41 | ] 42 | 43 | _css_dist = [] 44 | 45 | 46 | for _component in _components: 47 | setattr(_this_module, _component.__name__, _component) 48 | setattr(_component, '_js_dist', _js_dist) 49 | setattr(_component, '_css_dist', _css_dist) -------------------------------------------------------------------------------- /dash_dual_listbox/_imports_.py: -------------------------------------------------------------------------------- 1 | from .DualList import DualList 2 | 3 | 4 | __all__ = [ 5 | "DualList", 6 | ] 7 | -------------------------------------------------------------------------------- /dash_dual_listbox/bundle.js: -------------------------------------------------------------------------------- 1 | window.dash_dual_listbox=function(e){var t={};function o(n){if(t[n])return t[n].exports;var l=t[n]={i:n,l:!1,exports:{}};return e[n].call(l.exports,l,l.exports,o),l.l=!0,l.exports}return o.m=e,o.c=t,o.d=function(e,t,n){o.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},o.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},o.t=function(e,t){if(1&t&&(e=o(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(o.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var l in e)o.d(n,l,function(t){return e[t]}.bind(null,l));return n},o.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return o.d(t,"a",t),t},o.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},o.p="",o(o.s=1)}([function(e,t){e.exports=window.React},function(e,t,o){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.DualList=void 0;var n=function(e){return e&&e.__esModule?e:{default:e}}(o(2));t.DualList=n.default},function(e,t,o){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var n=function(){function e(e,t){for(var o=0;o"),moveAllRightIcon:a.default.createElement("span",{style:{fontSize:"14px",fontWeight:"bold"}},">>"),moveUpIcon:a.default.createElement("span",{style:{fontSize:"14px",fontWeight:"bold",color:"#000"}},"↑"),moveTopIcon:a.default.createElement("span",{style:{fontSize:"14px",fontWeight:"bold",color:"#000"}},"⇈"),moveDownIcon:a.default.createElement("span",{style:{fontSize:"14px",fontWeight:"bold",color:"#000"}},"↓"),moveBottomIcon:a.default.createElement("span",{style:{fontSize:"14px",fontWeight:"bold",color:"#000"}},"⇊")},t.default=i}]); -------------------------------------------------------------------------------- /dash_dual_listbox/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "src/lib/components/DualList.react.js": { 3 | "description": "", 4 | "displayName": "DualList", 5 | "methods": [ 6 | { 7 | "name": "onMove", 8 | "docblock": null, 9 | "modifiers": [], 10 | "params": [ 11 | { 12 | "name": "selected", 13 | "type": null 14 | } 15 | ], 16 | "returns": null 17 | } 18 | ], 19 | "props": { 20 | "id": { 21 | "type": { 22 | "name": "string" 23 | }, 24 | "required": false, 25 | "description": "The ID of this component, used to identify dash components\r\nin callbacks. The ID needs to be unique across all of the\r\ncomponents in an app." 26 | }, 27 | "selected": { 28 | "type": { 29 | "name": "arrayOf", 30 | "value": { 31 | "name": "union", 32 | "value": [ 33 | { 34 | "name": "string" 35 | } 36 | ] 37 | } 38 | }, 39 | "required": true, 40 | "description": "List of selected options, will appear in the right box" 41 | }, 42 | "available": { 43 | "type": { 44 | "name": "arrayOf", 45 | "value": { 46 | "name": "union", 47 | "value": [ 48 | { 49 | "name": "shape", 50 | "value": { 51 | "value": { 52 | "name": "any", 53 | "required": true 54 | }, 55 | "label": { 56 | "name": "string", 57 | "required": true 58 | } 59 | } 60 | }, 61 | { 62 | "name": "shape", 63 | "value": { 64 | "value": { 65 | "name": "any", 66 | "required": false 67 | }, 68 | "options": { 69 | "name": "arrayOf", 70 | "value": { 71 | "name": "shape", 72 | "value": { 73 | "value": { 74 | "name": "any", 75 | "required": true 76 | }, 77 | "label": { 78 | "name": "string", 79 | "required": true 80 | } 81 | } 82 | }, 83 | "required": false 84 | } 85 | } 86 | } 87 | ] 88 | } 89 | }, 90 | "required": true, 91 | "description": "List of available options, will appear in the left box" 92 | }, 93 | "leftLabel": { 94 | "type": { 95 | "name": "string" 96 | }, 97 | "required": false, 98 | "description": "A header for the left (available) list" 99 | }, 100 | "rightLabel": { 101 | "type": { 102 | "name": "string" 103 | }, 104 | "required": false, 105 | "description": "A header for the right (selected) list" 106 | }, 107 | "searchable": { 108 | "type": { 109 | "name": "bool" 110 | }, 111 | "required": false, 112 | "description": "A false value will hide the search field on the top" 113 | }, 114 | "sortable": { 115 | "type": { 116 | "name": "bool" 117 | }, 118 | "required": false, 119 | "description": "A false value will hide the reorder buttons on the right" 120 | }, 121 | "moveLeftIcon": { 122 | "type": { 123 | "name": "string" 124 | }, 125 | "required": false, 126 | "description": "fontawesome icons or icon of your choice" 127 | }, 128 | "moveRightIcon": { 129 | "type": { 130 | "name": "string" 131 | }, 132 | "required": false, 133 | "description": "fontawesome icons or icon of your choice" 134 | }, 135 | "moveAllLeftIcon": { 136 | "type": { 137 | "name": "string" 138 | }, 139 | "required": false, 140 | "description": "fontawesome icons or icon of your choice" 141 | }, 142 | "moveAllRightIcon": { 143 | "type": { 144 | "name": "string" 145 | }, 146 | "required": false, 147 | "description": "fontawesome icons or icon of your choice" 148 | }, 149 | "moveUpIcon": { 150 | "type": { 151 | "name": "string" 152 | }, 153 | "required": false, 154 | "description": "fontawesome icons or icon of your choice" 155 | }, 156 | "moveTopIcon": { 157 | "type": { 158 | "name": "string" 159 | }, 160 | "required": false, 161 | "description": "fontawesome icons or icon of your choice" 162 | }, 163 | "moveDownIcon": { 164 | "type": { 165 | "name": "string" 166 | }, 167 | "required": false, 168 | "description": "fontawesome icons or icon of your choice" 169 | }, 170 | "moveBottomIcon": { 171 | "type": { 172 | "name": "string" 173 | }, 174 | "required": false, 175 | "description": "fontawesome icons or icon of your choice" 176 | }, 177 | "setProps": { 178 | "type": { 179 | "name": "func" 180 | }, 181 | "required": false, 182 | "description": "" 183 | } 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /dash_dual_listbox/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dash-dual-listbox", 3 | "version": "0.0.1", 4 | "description": "Dual listbox for Dash. Original component: https://rawgit.com/jyotirmaybanerjee/react-duallist/master/example/examples.html# ", 5 | "main": "build/index.js", 6 | "scripts": { 7 | "start": "webpack-serve ./webpack.serve.config.js --open", 8 | "build:js-dev": "webpack --mode development", 9 | "build:js": "webpack --mode production", 10 | "build:py": "node ./extract-meta src/lib/components > dash_dual_listbox/metadata.json && copyfiles package.json dash_dual_listbox && python -c \"import dash; dash.development.component_loader.generate_classes('dash_dual_listbox', 'dash_dual_listbox/metadata.json')\"", 11 | "build:all": "npm run build:js & npm run build:py", 12 | "build:all-dev": "npm run build:js-dev & npm run build:py" 13 | }, 14 | "author": "Vivek Shankar vivekvs1@gmail.com", 15 | "license": "MIT", 16 | "dependencies": { 17 | "ramda": "^0.25.0", 18 | "react": "15.4.2", 19 | "react-dom": "15.4.2", 20 | "react-duallist": "^1.1.5" 21 | }, 22 | "devDependencies": { 23 | "babel-core": "^6.26.3", 24 | "babel-eslint": "^8.2.3", 25 | "babel-loader": "^7.1.4", 26 | "copyfiles": "^2.0.0", 27 | "babel-preset-env": "^1.7.0", 28 | "babel-preset-react": "^6.24.1", 29 | "css-loader": "^0.28.11", 30 | "eslint": "^4.19.1", 31 | "eslint-config-prettier": "^2.9.0", 32 | "eslint-plugin-import": "^2.12.0", 33 | "eslint-plugin-react": "^7.9.1", 34 | "npm": "^6.1.0", 35 | "react-docgen": "^2.20.1", 36 | "style-loader": "^0.21.0", 37 | "webpack": "^4.8.3", 38 | "webpack-cli": "^2.1.3", 39 | "webpack-serve": "^1.0.2" 40 | }, 41 | "peerDependencies": { 42 | "react": ">=0.14", 43 | "react-dom": ">=0.14" 44 | }, 45 | "engines": { 46 | "node": ">=8.11.0", 47 | "npm": ">=6.1.0" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /dash_dual_listbox/style.css: -------------------------------------------------------------------------------- 1 | .react-listbox-dual-list { 2 | display: flex; 3 | align-items: center; 4 | box-sizing: border-box; 5 | font-family: Roboto, sans-serif; 6 | } 7 | 8 | .react-listbox-dual-list * { 9 | box-sizing: border-box; 10 | } 11 | 12 | .react-listbox-dual-list input:disabled, .react-listbox-dual-list select:disabled { 13 | background: #eee; 14 | cursor: not-allowed; 15 | } 16 | 17 | .react-listbox-dual-list button, .react-listbox-dual-list select { 18 | line-height: 1.42857; 19 | font-family: inherit; 20 | } 21 | 22 | .react-listbox-dual-list .react-listbox-list-box { 23 | display: flex; 24 | flex: 1 1 0; 25 | flex-direction: column; 26 | align-self: stretch; 27 | } 28 | 29 | .react-listbox-dual-list .react-listbox-list-box .search-bar { 30 | margin-bottom: 5px; 31 | } 32 | 33 | .react-listbox-dual-list .react-listbox-list-box select { 34 | min-height: 150px; 35 | } 36 | 37 | .react-listbox-dual-list .react-listbox-list-box .list-filter, .react-listbox-dual-list .react-listbox-list-box .list-control { 38 | display: block; 39 | border: 1px solid #ccc; 40 | border-radius: 2px; 41 | padding: 8px 12px; 42 | width: 100%; 43 | color: #333; 44 | font-size: 14px; 45 | } 46 | 47 | .react-listbox-dual-list .react-listbox-list-box .list-filter { 48 | margin-bottom: 10px; 49 | } 50 | 51 | .react-listbox-dual-list .react-listbox-center-toolbar { 52 | display: flex; 53 | flex: 0 0 auto; 54 | flex-direction: column; 55 | margin: 55px 10px 0 10px; 56 | } 57 | 58 | .react-listbox-dual-list .react-listbox-center-toolbar .move-left, .react-listbox-dual-list .react-listbox-center-toolbar .move-right { 59 | display: flex; 60 | flex-direction: column; 61 | } 62 | 63 | .react-listbox-dual-list .react-listbox-center-toolbar .move-right { 64 | margin-bottom: 10px; 65 | } 66 | 67 | .react-listbox-dual-list .react-listbox-right-toolbar { 68 | display: flex; 69 | flex: 0 0 auto; 70 | flex-direction: column; 71 | margin: 55px 10px 0 10px; 72 | } 73 | 74 | .react-listbox-dual-list .react-listbox-right-toolbar .move-top, .react-listbox-dual-list .react-listbox-right-toolbar .move-bottom { 75 | display: flex; 76 | flex-direction: column; 77 | } 78 | 79 | .react-listbox-dual-list .react-listbox-right-toolbar .move-top { 80 | margin-bottom: 10px; 81 | } 82 | 83 | .react-listbox-dual-list .btn-move { 84 | margin-bottom: 5px; 85 | border: 1px solid #ccc; 86 | border-radius: 2px; 87 | background: #fff; 88 | cursor: pointer; 89 | padding: 5px 10px; 90 | color: #333; 91 | font-size: 12px; 92 | } 93 | 94 | .react-listbox-dual-list .btn-move:active:not(:disabled), .react-listbox-dual-list .btn-move:focus:not(:disabled) { 95 | border-color: #8c8c8c; 96 | background: #e6e6e6; 97 | } 98 | 99 | .react-listbox-dual-list .btn-move:focus:not(:disabled) { 100 | outline: thin dotted; 101 | outline-offset: -2px; 102 | } 103 | 104 | .react-listbox-dual-list .btn-move:hover:not(:disabled) { 105 | border-color: #adadad; 106 | background: #e6e6e6; 107 | } 108 | 109 | .react-listbox-dual-list .btn-move:disabled { 110 | opacity: 0.5; 111 | cursor: not-allowed; 112 | } 113 | 114 | .react-listbox-dual-list .btn-move:last-child { 115 | margin-bottom: 0; 116 | } 117 | 118 | .react-listbox-dual-list .btn-move i { 119 | margin: 0 -1px; 120 | } 121 | 122 | .react-listbox-dual-list .list-container { 123 | display: flex; 124 | flex: 1 0 auto; 125 | } 126 | 127 | .react-listbox-dual-list .list-label { 128 | position: absolute; 129 | clip: rect(0 0 0 0); 130 | } 131 | 132 | .react-listbox-dual-list .list-control { 133 | flex: 1 0 auto; 134 | } 135 | 136 | .react-listbox-dual-list .list-control optgroup { 137 | font: inherit; 138 | font-weight: 700; 139 | } -------------------------------------------------------------------------------- /extract-meta: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const reactDocs = require('react-docgen'); 6 | 7 | const componentPaths = process.argv.slice(2); 8 | if (!componentPaths.length) { 9 | help(); 10 | process.exit(1); 11 | } 12 | 13 | const metadata = Object.create(null); 14 | componentPaths.forEach(componentPath => 15 | collectMetadataRecursively(componentPath) 16 | ); 17 | writeOut(metadata); 18 | 19 | function help() { 20 | console.error('usage: '); 21 | console.error( 22 | 'extract-meta path/to/component(s) ' + 23 | ' [path/to/more/component(s), ...] > metadata.json' 24 | ); 25 | } 26 | 27 | function writeError(msg, filePath) { 28 | if (filePath) { 29 | process.stderr.write(`Error with path ${filePath}`); 30 | } 31 | 32 | process.stderr.write(msg + '\n'); 33 | if (msg instanceof Error) { 34 | process.stderr.write(msg.stack + '\n'); 35 | } 36 | } 37 | 38 | function parseFile(filepath) { 39 | const urlpath = filepath.split(path.sep).join('/'); 40 | let src; 41 | 42 | if (!['.jsx', '.js'].includes(path.extname(filepath))) { 43 | return; 44 | } 45 | 46 | try { 47 | src = fs.readFileSync(filepath); 48 | metadata[urlpath] = reactDocs.parse(src); 49 | } catch (error) { 50 | writeError(error, filepath); 51 | } 52 | } 53 | 54 | function collectMetadataRecursively(componentPath) { 55 | if (fs.lstatSync(componentPath).isDirectory()) { 56 | let dirs; 57 | try { 58 | dirs = fs.readdirSync(componentPath); 59 | } catch (error) { 60 | writeError(error, componentPath); 61 | } 62 | dirs.forEach(filename => { 63 | const filepath = path.join(componentPath, filename); 64 | if (fs.lstatSync(filepath).isDirectory()) { 65 | collectMetadataRecursively(filepath); 66 | } else { 67 | parseFile(filepath); 68 | } 69 | }); 70 | } else { 71 | parseFile(componentPath); 72 | } 73 | } 74 | 75 | function writeOut(result) { 76 | console.log(JSON.stringify(result, '\t', 2)); 77 | } 78 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | dash-dual-listbox 5 | 6 | 7 |
8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dash-dual-listbox", 3 | "version": "0.0.1", 4 | "description": "Dual listbox for Dash. Original component: https://rawgit.com/jyotirmaybanerjee/react-duallist/master/example/examples.html# ", 5 | "main": "build/index.js", 6 | "scripts": { 7 | "start": "webpack-serve ./webpack.serve.config.js --open", 8 | "build:js-dev": "webpack --mode development", 9 | "build:js": "webpack --mode production", 10 | "build:py": "node ./extract-meta src/lib/components > dash_dual_listbox/metadata.json && copyfiles package.json dash_dual_listbox && python -c \"import dash; dash.development.component_loader.generate_classes('dash_dual_listbox', 'dash_dual_listbox/metadata.json')\"", 11 | "build:all": "npm run build:js & npm run build:py", 12 | "build:all-dev": "npm run build:js-dev & npm run build:py" 13 | }, 14 | "author": "Vivek Shankar vivekvs1@gmail.com", 15 | "license": "MIT", 16 | "dependencies": { 17 | "ramda": "^0.25.0", 18 | "react": "15.4.2", 19 | "react-dom": "15.4.2", 20 | "react-duallist": "^1.1.5" 21 | }, 22 | "devDependencies": { 23 | "babel-core": "^6.26.3", 24 | "babel-eslint": "^8.2.3", 25 | "babel-loader": "^7.1.4", 26 | "copyfiles": "^2.0.0", 27 | "babel-preset-env": "^1.7.0", 28 | "babel-preset-react": "^6.24.1", 29 | "css-loader": "^0.28.11", 30 | "eslint": "^4.19.1", 31 | "eslint-config-prettier": "^2.9.0", 32 | "eslint-plugin-import": "^2.12.0", 33 | "eslint-plugin-react": "^7.9.1", 34 | "npm": "^6.1.0", 35 | "react-docgen": "^2.20.1", 36 | "style-loader": "^0.21.0", 37 | "webpack": "^4.8.3", 38 | "webpack-cli": "^2.1.3", 39 | "webpack-serve": "^1.0.2" 40 | }, 41 | "peerDependencies": { 42 | "react": ">=0.14", 43 | "react-dom": ">=0.14" 44 | }, 45 | "engines": { 46 | "node": ">=8.11.0", 47 | "npm": ">=6.1.0" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | from setuptools import setup 4 | 5 | 6 | with open(os.path.join('dash_dual_listbox', 'package.json')) as f: 7 | package = json.load(f) 8 | 9 | package_name = package["name"].replace(" ", "_").replace("-", "_") 10 | 11 | setup( 12 | name=package_name, 13 | version=package["version"], 14 | author=package['author'], 15 | packages=[package_name], 16 | include_package_data=True, 17 | license=package['license'], 18 | description=package['description'] if 'description' in package else package_name, 19 | install_requires=[] 20 | ) 21 | -------------------------------------------------------------------------------- /src/demo/App.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {DualList} from '../lib'; 3 | import './style.css'; 4 | 5 | class App extends Component { 6 | 7 | constructor(props) { 8 | super(props); 9 | this.state = { 10 | available: [ 11 | {label: 'sdf', value: 'AL'}, 12 | {label: 'Alassdfsdfka', value: 'AK'}, 13 | {label: 'Arizona', value: 'AZ'}, 14 | {label: 'Arkansas', value: 'AR'}, 15 | {label: 'California', value: 'CA'}, 16 | {label: 'Colorado', value: 'CO'}, 17 | {label: 'sdfg', value: 'CT'}, 18 | {label: 'Delaware', value: 'DE'}, 19 | {label: 'Florida', value: 'FL'}, 20 | {label: 'Georgia', value: 'GA'}, 21 | ], 22 | selected: ['AL', 'CA', 'AK'], 23 | } 24 | 25 | } 26 | 27 | render() { 28 | const {available, selected} = this.state; 29 | const leftLabel = ['Left Editable label'] 30 | const rightLabel = ['Right Editable label'] 31 | 32 | 33 | return ( 34 |
38 | 39 | ); 40 | } 41 | } 42 | 43 | export default App; 44 | -------------------------------------------------------------------------------- /src/demo/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | 6 | ReactDOM.render(, document.getElementById('root')); 7 | -------------------------------------------------------------------------------- /src/demo/style.css: -------------------------------------------------------------------------------- 1 | .react-listbox-dual-list { 2 | display: flex; 3 | align-items: center; 4 | box-sizing: border-box; 5 | font-family: Roboto, sans-serif; 6 | } 7 | 8 | .react-listbox-dual-list * { 9 | box-sizing: border-box; 10 | } 11 | 12 | .react-listbox-dual-list input:disabled, .react-listbox-dual-list select:disabled { 13 | background: #eee; 14 | cursor: not-allowed; 15 | } 16 | 17 | .react-listbox-dual-list button, .react-listbox-dual-list select { 18 | line-height: 1.42857; 19 | font-family: inherit; 20 | } 21 | 22 | .react-listbox-dual-list .react-listbox-list-box { 23 | display: flex; 24 | flex: 1 1 0; 25 | flex-direction: column; 26 | align-self: stretch; 27 | } 28 | 29 | .react-listbox-dual-list .react-listbox-list-box .search-bar { 30 | margin-bottom: 5px; 31 | } 32 | 33 | .react-listbox-dual-list .react-listbox-list-box select { 34 | min-height: 150px; 35 | } 36 | 37 | .react-listbox-dual-list .react-listbox-list-box .list-filter, .react-listbox-dual-list .react-listbox-list-box .list-control { 38 | display: block; 39 | border: 1px solid #ccc; 40 | border-radius: 2px; 41 | padding: 8px 12px; 42 | width: 100%; 43 | color: #333; 44 | font-size: 14px; 45 | } 46 | 47 | .react-listbox-dual-list .react-listbox-list-box .list-filter { 48 | margin-bottom: 10px; 49 | } 50 | 51 | .react-listbox-dual-list .react-listbox-center-toolbar { 52 | display: flex; 53 | flex: 0 0 auto; 54 | flex-direction: column; 55 | margin: 55px 10px 0 10px; 56 | } 57 | 58 | .react-listbox-dual-list .react-listbox-center-toolbar .move-left, .react-listbox-dual-list .react-listbox-center-toolbar .move-right { 59 | display: flex; 60 | flex-direction: column; 61 | } 62 | 63 | .react-listbox-dual-list .react-listbox-center-toolbar .move-right { 64 | margin-bottom: 10px; 65 | } 66 | 67 | .react-listbox-dual-list .react-listbox-right-toolbar { 68 | display: flex; 69 | flex: 0 0 auto; 70 | flex-direction: column; 71 | margin: 55px 10px 0 10px; 72 | } 73 | 74 | .react-listbox-dual-list .react-listbox-right-toolbar .move-top, .react-listbox-dual-list .react-listbox-right-toolbar .move-bottom { 75 | display: flex; 76 | flex-direction: column; 77 | } 78 | 79 | .react-listbox-dual-list .react-listbox-right-toolbar .move-top { 80 | margin-bottom: 10px; 81 | } 82 | 83 | .react-listbox-dual-list .btn-move { 84 | margin-bottom: 5px; 85 | border: 1px solid #ccc; 86 | border-radius: 2px; 87 | background: #fff; 88 | cursor: pointer; 89 | padding: 5px 10px; 90 | color: #333; 91 | font-size: 12px; 92 | } 93 | 94 | .react-listbox-dual-list .btn-move:active:not(:disabled), .react-listbox-dual-list .btn-move:focus:not(:disabled) { 95 | border-color: #8c8c8c; 96 | background: #e6e6e6; 97 | } 98 | 99 | .react-listbox-dual-list .btn-move:focus:not(:disabled) { 100 | outline: thin dotted; 101 | outline-offset: -2px; 102 | } 103 | 104 | .react-listbox-dual-list .btn-move:hover:not(:disabled) { 105 | border-color: #adadad; 106 | background: #e6e6e6; 107 | } 108 | 109 | .react-listbox-dual-list .btn-move:disabled { 110 | opacity: 0.5; 111 | cursor: not-allowed; 112 | } 113 | 114 | .react-listbox-dual-list .btn-move:last-child { 115 | margin-bottom: 0; 116 | } 117 | 118 | .react-listbox-dual-list .btn-move i { 119 | margin: 0 -1px; 120 | } 121 | 122 | .react-listbox-dual-list .list-container { 123 | display: flex; 124 | flex: 1 0 auto; 125 | } 126 | 127 | .react-listbox-dual-list .list-label { 128 | position: absolute; 129 | clip: rect(0 0 0 0); 130 | } 131 | 132 | .react-listbox-dual-list .list-control { 133 | flex: 1 0 auto; 134 | } 135 | 136 | .react-listbox-dual-list .list-control optgroup { 137 | font: inherit; 138 | font-weight: 700; 139 | } -------------------------------------------------------------------------------- /src/lib/components/DualList.react.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React, {Component} from 'react'; 3 | import Duallist from 'react-duallist'; 4 | 5 | class DualList extends Component { 6 | 7 | constructor(props) { 8 | super(props); 9 | this.state = { 10 | selected: props.selected, 11 | } 12 | 13 | this.onMove = this.onMove.bind(this); 14 | } 15 | 16 | 17 | onMove(selected) { 18 | this.setState({selected}); 19 | 20 | const {setProps} = this.props; 21 | if (setProps) { 22 | setProps({selected: selected}) 23 | } 24 | 25 | } 26 | 27 | 28 | render() { 29 | 30 | 31 | return ( 32 |
33 | 36 |
37 | 38 | ); 39 | } 40 | } 41 | 42 | export default DualList; 43 | 44 | 45 | DualList.propTypes = { 46 | 47 | /** 48 | * The ID of this component, used to identify dash components 49 | * in callbacks. The ID needs to be unique across all of the 50 | * components in an app. 51 | */ 52 | id: PropTypes.string, 53 | 54 | /** 55 | * List of selected options, will appear in the right box 56 | */ 57 | selected: PropTypes.arrayOf( 58 | PropTypes.oneOfType([PropTypes.string])).isRequired, 59 | 60 | /** 61 | * List of available options, will appear in the left box 62 | */ 63 | available: PropTypes.arrayOf( 64 | PropTypes.oneOfType([ 65 | PropTypes.shape({ 66 | value: PropTypes.any.isRequired, 67 | label: PropTypes.string.isRequired, 68 | }), 69 | PropTypes.shape({ 70 | value: PropTypes.any, 71 | options: PropTypes.arrayOf(PropTypes.shape({ 72 | value: PropTypes.any.isRequired, 73 | label: PropTypes.string.isRequired 74 | })) 75 | }), 76 | ]), 77 | ).isRequired, 78 | 79 | /** 80 | * A header for the left (available) list 81 | */ 82 | leftLabel: PropTypes.string, 83 | 84 | /** 85 | * A header for the right (selected) list 86 | */ 87 | rightLabel: PropTypes.string, 88 | 89 | /** 90 | * A false value will hide the search field on the top 91 | */ 92 | searchable: PropTypes.bool, 93 | 94 | /** 95 | * A false value will hide the reorder buttons on the right 96 | */ 97 | sortable: PropTypes.bool, 98 | 99 | /** 100 | * fontawesome icons or icon of your choice 101 | */ 102 | moveLeftIcon: PropTypes.string, 103 | 104 | /** 105 | * fontawesome icons or icon of your choice 106 | */ 107 | moveRightIcon: PropTypes.string, 108 | 109 | /** 110 | * fontawesome icons or icon of your choice 111 | */ 112 | moveAllLeftIcon: PropTypes.string, 113 | 114 | /** 115 | * fontawesome icons or icon of your choice 116 | */ 117 | moveAllRightIcon: PropTypes.string, 118 | 119 | /** 120 | * fontawesome icons or icon of your choice 121 | */ 122 | moveUpIcon: PropTypes.string, 123 | 124 | /** 125 | * fontawesome icons or icon of your choice 126 | */ 127 | moveTopIcon: PropTypes.string, 128 | 129 | /** 130 | * fontawesome icons or icon of your choice 131 | */ 132 | moveDownIcon: PropTypes.string, 133 | 134 | /** 135 | * fontawesome icons or icon of your choice 136 | */ 137 | moveBottomIcon: PropTypes.string, 138 | 139 | 140 | setProps: PropTypes.func 141 | } -------------------------------------------------------------------------------- /src/lib/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/prefer-default-export */ 2 | import DualList from './components/DualList.react.js'; 3 | 4 | export { 5 | DualList 6 | }; 7 | -------------------------------------------------------------------------------- /tests/IntegrationTests.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | import logging 4 | import os 5 | import multiprocessing 6 | import sys 7 | import time 8 | import unittest 9 | import percy 10 | import threading 11 | import platform 12 | import flask 13 | import requests 14 | 15 | from selenium import webdriver 16 | from selenium.webdriver.chrome.options import Options 17 | 18 | 19 | class IntegrationTests(unittest.TestCase): 20 | def percy_snapshot(self, name=''): 21 | if os.environ.get('PERCY_ENABLED', False): 22 | snapshot_name = '{} - {}'.format(name, sys.version_info) 23 | self.percy_runner.snapshot( 24 | name=snapshot_name 25 | ) 26 | 27 | @classmethod 28 | def setUpClass(cls): 29 | super(IntegrationTests, cls).setUpClass() 30 | 31 | options = Options() 32 | if 'DASH_TEST_CHROMEPATH' in os.environ: 33 | options.binary_location = os.environ['DASH_TEST_CHROMEPATH'] 34 | 35 | cls.driver = webdriver.Chrome(chrome_options=options) 36 | 37 | if os.environ.get('PERCY_ENABLED', False): 38 | loader = percy.ResourceLoader( 39 | webdriver=cls.driver 40 | ) 41 | cls.percy_runner = percy.Runner(loader=loader) 42 | cls.percy_runner.initialize_build() 43 | 44 | @classmethod 45 | def tearDownClass(cls): 46 | super(IntegrationTests, cls).tearDownClass() 47 | 48 | cls.driver.quit() 49 | if os.environ.get('PERCY_ENABLED', False): 50 | cls.percy_runner.finalize_build() 51 | 52 | def setUp(self): 53 | pass 54 | 55 | def tearDown(self): 56 | time.sleep(3) 57 | if platform.system() == 'Windows': 58 | requests.get('http://localhost:8050/stop') 59 | else: 60 | self.server_process.terminate() 61 | time.sleep(3) 62 | 63 | def startServer(self, app): 64 | if 'DASH_TEST_PROCESSES' in os.environ: 65 | processes = int(os.environ['DASH_TEST_PROCESSES']) 66 | else: 67 | processes = 4 68 | 69 | def run(): 70 | app.scripts.config.serve_locally = True 71 | app.css.config.serve_locally = True 72 | app.run_server( 73 | port=8050, 74 | debug=False, 75 | processes=processes 76 | ) 77 | 78 | def run_windows(): 79 | app.scripts.config.serve_locally = True 80 | app.css.config.serve_locally = True 81 | 82 | @app.server.route('/stop') 83 | def _stop_server_windows(): 84 | stopper = flask.request.environ['werkzeug.server.shutdown'] 85 | stopper() 86 | return 'stop' 87 | 88 | app.run_server( 89 | port=8050, 90 | debug=False, 91 | threaded=True 92 | ) 93 | 94 | # Run on a separate process so that it doesn't block 95 | 96 | system = platform.system() 97 | if system == 'Windows': 98 | self.server_thread = threading.Thread(target=run_windows) 99 | self.server_thread.start() 100 | else: 101 | self.server_process = multiprocessing.Process(target=run) 102 | self.server_process.start() 103 | logging.getLogger('werkzeug').setLevel(logging.ERROR) 104 | time.sleep(5) 105 | 106 | # Visit the dash page 107 | self.driver.get('http://localhost:8050') 108 | time.sleep(0.5) 109 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vivekvs1/dash-dual-listbox/9067f3d93a022c00155bc68b7bdc55c7ab530097/tests/__init__.py -------------------------------------------------------------------------------- /tests/requirements.txt: -------------------------------------------------------------------------------- 1 | # Switch into a virtual environment 2 | # pip install -r requirements.txt 3 | 4 | chromedriver-binary 5 | dash 6 | dash-core-components 7 | dash-html-components 8 | dash-renderer 9 | ipdb 10 | percy 11 | selenium 12 | flake8 13 | pylint 14 | -------------------------------------------------------------------------------- /tests/test_render.py: -------------------------------------------------------------------------------- 1 | from .IntegrationTests import IntegrationTests 2 | import dash 3 | import dash_html_components as html 4 | from selenium.webdriver.common.by import By 5 | from selenium.webdriver.support.ui import WebDriverWait 6 | from selenium.webdriver.support import expected_conditions as EC 7 | 8 | from dash_color_picker import ColorPicker # pylint: disable=no-name-in-module 9 | 10 | 11 | class Tests(IntegrationTests): 12 | def test_render_component(self): 13 | app = dash.Dash(__name__) 14 | app.layout = html.Div([ 15 | html.Div(id='waitfor'), 16 | ColorPicker(id='DualList', color='#f22') 17 | ]) 18 | 19 | self.startServer(app) 20 | 21 | WebDriverWait(self.driver, 10).until( 22 | EC.presence_of_element_located((By.ID, "waitfor")) 23 | ) 24 | 25 | self.percy_snapshot('Simple Render') 26 | -------------------------------------------------------------------------------- /usage.py: -------------------------------------------------------------------------------- 1 | from dash_dual_listbox import DualList 2 | import dash 3 | from dash.dependencies import Input, Output 4 | import dash_html_components as html 5 | 6 | app = dash.Dash('') 7 | 8 | app.scripts.config.serve_locally = True 9 | app.css.config.serve_locally = True 10 | 11 | app.layout = html.Div([ 12 | DualList(id='DualList', available=[{'label': 'sdf', 'value': 'AL'}, 13 | {'label': 'Alassdfsdfka', 'value': 'AK'}, 14 | {'label': 'Arizona', 'value': 'AZ'}, 15 | {'label': 'Arkansas', 'value': 'AR'}, 16 | {'label': 'California', 'value': 'CA'}, 17 | {'label': 'Colorado', 'value': 'CO'}, 18 | {'label': 'sdfg', 'value': 'CT'}, 19 | {'label': 'Delaware', 'value': 'DE'}, 20 | {'label': 'Florida', 'value': 'FL'}, 21 | {'label': 'Georgia', 'value': 'GA'}], selected=['AL']), 22 | ]) 23 | 24 | 25 | # @app.callback(Output('display', 'children'), 26 | # [Input('DualList', 'selected'), 27 | # ]) 28 | # def display_output(c): 29 | # return c[0] 30 | 31 | 32 | if __name__ == '__main__': 33 | app.run_server(debug=True) 34 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const packagejson = require('./package.json'); 3 | 4 | const dashLibraryName = packagejson.name.replace(/-/g, '_'); 5 | 6 | module.exports = { 7 | entry: {main: './src/lib/index.js'}, 8 | output: { 9 | path: path.resolve(__dirname, dashLibraryName), 10 | filename: 'bundle.js', 11 | library: dashLibraryName, 12 | libraryTarget: 'window', 13 | }, 14 | externals: { 15 | react: 'React', 16 | 'react-dom': 'ReactDOM', 17 | 'plotly.js': 'Plotly', 18 | }, 19 | module: { 20 | rules: [ 21 | { 22 | test: /\.js$/, 23 | exclude: /node_modules/, 24 | use: { 25 | loader: 'babel-loader', 26 | }, 27 | }, 28 | { 29 | test: /\.css$/, 30 | use: [ 31 | { 32 | loader: 'style-loader', 33 | }, 34 | { 35 | loader: 'css-loader', 36 | }, 37 | ], 38 | }, 39 | ], 40 | }, 41 | }; 42 | -------------------------------------------------------------------------------- /webpack.serve.config.js: -------------------------------------------------------------------------------- 1 | const config = require('./webpack.config.js'); 2 | 3 | config.entry = {main: './src/demo/index.js'}; 4 | config.output = {filename: 'output.js'}; 5 | config.mode = 'development'; 6 | config.externals = undefined; // eslint-disable-line 7 | config.devtool = 'inline-source-map'; 8 | module.exports = config; 9 | --------------------------------------------------------------------------------