Updates
@@ -1,8 +0,0 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"extends": "airbnb-base",
|
||||
"env": {
|
||||
"browser": true
|
||||
},
|
||||
"rules": {
|
||||
"no-console": "off",
|
||||
"no-underscore-dangle": "off",
|
||||
"no-plusplus": "off",
|
||||
"no-cond-assign": "off",
|
||||
"func-names": "off",
|
||||
"no-continue": "off",
|
||||
"no-bitwise": "off",
|
||||
"class-methods-use-this": "off",
|
||||
"prefer-destructuring": "off",
|
||||
"no-param-reassign": [
|
||||
"off"
|
||||
],
|
||||
"max-len": "off",
|
||||
"no-multi-spaces": "off"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
node_modules
|
||||
npm-debug.log
|
||||
.DS_Store
|
||||
.idea
|
||||
.vscode
|
||||
package-lock.json
|
||||
yarn-error.log
|
||||
@@ -1,7 +0,0 @@
|
||||
.editorconfig
|
||||
.idea
|
||||
.eslintrc.json
|
||||
index.html
|
||||
server.js
|
||||
demo
|
||||
dist/demo
|
||||
|
Before Width: | Height: | Size: 4.9 KiB |
@@ -1,195 +0,0 @@
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="512px" height="512px" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path fill="#C56528" d="M246.5,197.8c-2.3-16.2-4.8-32.4-8.1-48.4c-3.2-15.1-7.2-30.1-9.2-45.4c-1.9-14.3-1-29.7-6.9-43.2
|
||||
c-2.1-4.8-5.7-12.1-11.7-12.8c-4.9-0.5-8.6,4.1-10.7,7.9c-5.7,10.8-5.3,23.5-3.1,35.2c1.5,7.7,4.1,15.3,6.1,22.9
|
||||
c1.9,7.3,3.6,14.6,5,21.9c6.1,30.1,19.4,58.2,28.3,87.5c4.1-1.8,8.5-3.1,13-4C248.8,212.3,247.5,205,246.5,197.8z"></path>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path fill="#C56528" d="M214.6,246.3c-1.3,0.6-2.9,0.6-4.6-0.4c-5.5-3.3-10.9-6.8-16.1-10.6c-12.4-8.8-24.2-18.5-37-26.7
|
||||
c-12.7-8.1-26.8-14.2-39.8-22c-11.7-7-24.1-15.1-37.5-18.4c-4.9-1.2-12.2-3-16.9,0c-5,3.3-3.8,8.1-0.5,12.3
|
||||
c7.4,9.5,20.1,14.8,31.1,18.8c12.5,4.6,25.5,8,37.8,13.2c13.8,5.7,26.9,13,40.5,19.1c13.8,6.2,27.7,12.3,41,19.5
|
||||
c0.6-1.1,1.3-2.1,2-3.1C214.5,247.5,214.5,246.9,214.6,246.3z"></path>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path fill="#C56528" d="M191.5,114c-1.2-4.4-2.5-8.8-3.6-13.2c-9.1,3.8-17.9,8.2-26.1,13.7c-16.9,11.1-31.4,25.5-44.7,40.6
|
||||
c-0.2,0.3-0.4,0.7-0.6,1c-3.3,4-6.5,8-9.6,12c7.5,4.1,14.8,8.5,22.2,12.8c2.8-3.9,5.7-7.8,8.8-11.5c5.6-6.7,11.4-13.3,17.6-19.7
|
||||
c3.5-2.4,6.8-5,9.9-7.8c0.6-0.4,1.2-0.8,1.8-1.2c2.6-1.9,5.4-3.8,8.2-5.5c6.5-2.6,13-5.1,19.6-7.4
|
||||
C193.8,123.2,192.7,118.6,191.5,114z"></path>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path fill="#C56528" d="M373.7,62.3c-4.1-8.7-21.5,8.8-24.4,11.9c-17.9,19.2-22.2,46.1-31.4,69.8c-5.2,13.5-12.5,26.2-19.1,39.1
|
||||
c-6.5,12.6-12.6,25.4-17.6,38.7c2.3,0.7,4.5,1.6,6.7,2.5c12-18.7,24.4-37.2,36.3-56.1c15-24,31.5-48.2,42.6-74.3
|
||||
c2.5-6,5-12.1,6.5-18.5C374.2,71.5,375.5,66.3,373.7,62.3z"></path>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path fill="#C56528" d="M304.4,249.8c-3.9-4.9-8.7-9.2-14.1-12.5c-2.6,0.4-5.4-1.1-6.3-3.4c-1.4-0.6-2.8-1.2-4.2-1.7
|
||||
c-14.4-5-37.2-4.9-49,6.8c0.3,0.3,0.5,0.7,0.7,1c-3.4,2.8-6.1,7-8.3,10.6c-2.1,3.4-4.7,7.9-4.4,12.2c-5.4,13.9-3.5,29.9,8,41.6
|
||||
c13.2,13.4,33.2,17.8,51.2,12.4c17.6-5.2,32.2-19.7,34-38.6C312.9,268,311,258,304.4,249.8z"></path>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path fill="#C56528" d="M370.8,111.3c-3.9,7.6-8.2,15.1-12.6,22.5c5.2,3.3,10.3,6.9,15.1,10.8c18.6,15.2,33.2,34.4,43.3,55.7
|
||||
c7.9-2.7,15.8-5.5,23.9-7.9C425.6,159.3,402,130.2,370.8,111.3z"></path>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path fill="#C56528" d="M238.6,87.3c0.9,9.2,1.8,18.4,3.3,27.6c9-1.7,18.2-2.8,27.4-3.3c15.8-0.8,31.5,0.6,46.6,4.4
|
||||
c2.7-8.2,5.6-16.3,9.2-23.9C297.1,84.4,267.2,83.2,238.6,87.3z"></path>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path fill="#C56528" d="M94.5,271c1.4-19,6.3-37.5,14-54.8c-8.7-2.8-17.5-5.5-25.9-9.1c-8.1,17.8-13.4,37-15.1,56.5
|
||||
c-1.6,17.9-1.7,36,1.1,53.7c0.6-0.1,1.1-0.2,1.7-0.4c7.9-1.7,16-2.5,23.9-4.2c1-0.2,2.1-0.5,3.1-0.7
|
||||
C94.5,298.6,93.5,284.8,94.5,271z"></path>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path fill="#C56528" d="M455.2,240.2c-5.9,1.1-11.8,1.8-17.6,3.2c-2.4,0.6-4.7,1.2-7.1,1.9c0.7,4.5,1.3,9.1,1.6,13.7
|
||||
c1.8,25-2.2,49.9-11.2,72.9c8.8,2.4,17.7,4.8,26,8.2C457.7,307.8,460.9,273.6,455.2,240.2z"></path>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path fill="#C56528" d="M396.1,375.2c-2.2,2.8-4.6,5.6-7,8.4c-15.2,17-33.7,30.8-54.1,40.7c4.4,11,9.7,21.6,13.6,32.7
|
||||
c0.1,0.4,0.3,0.8,0.4,1.2c1.7-0.8,3.3-1.6,5-2.5c28.2-15,51.5-38.1,68.8-65C413.4,386.5,404.5,381,396.1,375.2z"></path>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path fill="#C56528" d="M479.1,200.2c-8.6-4.2-19.7-1.8-28.5,0.8c-15.4,4.5-30.5,9.7-45.7,14.9c-30.1,10.2-61.1,17.8-90.7,29.8
|
||||
c2.3,3.4,4.2,7,5.6,10.9c0.2,0.7,0.5,1.3,0.7,2c4.6-0.9,9.3-1.8,13.9-2.7c16-2.9,32.1-5.3,48-8.8c15.6-3.4,30.6-8.5,46-12.6
|
||||
c14.8-3.9,30.8-4,44-12.6C478.7,217.8,490.5,205.8,479.1,200.2z"></path>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path fill="#C56528" d="M458.5,359.8c-18.9-15.9-45-15.8-66.7-25.9c-24.8-11.6-46.8-29.5-73.7-36.6c-0.4-0.1-0.7-0.2-1-0.4
|
||||
c-1,2.2-2.2,4.2-3.5,6.2c16.6,10.7,32.8,21.9,48.8,33.5c22.5,16.4,44.2,36,70.5,46.3c6.1,2.4,12.5,4.4,19,5.2
|
||||
c4.5,0.5,11,1.3,15.1-1.3C477.3,380.1,464.7,364.9,458.5,359.8z"></path>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path fill="#C56528" d="M146.3,397.5c-13.8-12.5-25.3-27.4-33.9-43.8c-2.8,1.2-5.6,2.5-8.4,3.9c-6,3-12,6.1-18.2,8.8
|
||||
c4.7,8.3,10.3,16.1,16.7,23.4c14.7,16.6,32.3,30.2,51.4,41.4c2.8-5.5,5.8-10.8,9-16c0.6-1,1.3-2,2-3
|
||||
C158.3,407.6,152.1,402.8,146.3,397.5z"></path>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path fill="#C56528" d="M335.3,453.7c-6.3-14-12.5-28-16.4-42.9c-7.9-29.9-16.6-59.3-26.7-88.5c-4.1,2.2-8.5,3.9-13.1,5.1
|
||||
c0.1,1.7,0.2,3.3,0.3,5c2.4,31.9,10.7,62.8,17.1,94c1.6,7.9,3.2,15.9,4.5,23.9c1,6.4,1.4,13.1,3.7,19.2c0.2,0.4,0.3,0.8,0.5,1.2
|
||||
c0.4,0,0.7-0.1,1.1-0.1l1.1,4.4c5.2,8.6,14.7,16.1,24.9,11.3C345.5,480.2,339.7,463.5,335.3,453.7z"></path>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path fill="#C56528" d="M208.9,296.3c-1.3-3.2-2.2-6.4-2.8-9.7c-9.7,3.5-19.6,6.6-29.6,9.6c-15.2,4.6-30.5,9.5-45.3,15.3
|
||||
c-14.6,5.8-28.6,11-44.2,13.5c-12.3,2-26.8,3.5-37.2,11c-4.5,3.2-9.6,8.4-9,14.5c0.5,5.2,4.6,9.3,9.7,10.6
|
||||
c12.9,3.2,26.6-2.6,38.1-8.2c12.5-6,24.4-12,37.5-16.4c29.2-9.8,56.1-25.3,85.7-34.1C210.8,300.4,209.8,298.4,208.9,296.3z"></path>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path fill="#C56528" d="M228.2,319.2c-5.1,4.9-8.1,11.6-10.7,18c-5.2,13-10.6,25.9-16.7,38.5c-3.4,7-7.1,13.8-11.4,20.3
|
||||
c-3.7,5.7-8.2,11-11.9,16.7c-8,12-15.4,24.7-20.5,38.3c-2,5.3-4.9,11.8-4.1,17.6c0.6,4.8,4.9,8,9.9,6.5
|
||||
c12.5-3.8,19.6-19.1,25.3-29.7c7.2-13.4,13.6-27.5,17.8-42.2c3.9-13.8,7.4-27.3,12.9-40.6c5.4-13.1,11.4-25.9,15.5-39.5
|
||||
c0-0.1,0.1-0.2,0.1-0.4C232.2,321.7,230.1,320.5,228.2,319.2z"></path>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path fill="#C56528" d="M292,464.1c-1.6-8.2-2.9-16.4-4.3-24.6c-6,1-12.1,1.7-18.2,2c-21.3,1.2-42.8-2-63-8.9
|
||||
c-1.2,2.7-2.4,5.3-3.7,7.9c-1.9,3.8-3.8,7.8-5.9,11.8c7.8,3.2,15.8,6.1,23.9,8.7c23.7,7.5,48.7,11.8,73.2,10.6
|
||||
C293.1,469.2,292.4,466.7,292,464.1z"></path>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path d="M486,191.6c-8.6-5.6-19.8-5-29.4-3.2c-2.4,0.4-4.8,1-7.1,1.5c-13.9-31.5-35.5-59.7-63.6-79.8c-3.6-2.6-7.2-4.9-11-7.2
|
||||
c3.3-7.2,6.2-14.5,8.4-22.2c2.7-9.1,4.2-20.4-3.1-27.8c-8.4-8.4-21.9-1.1-29.3,4.8c-9.3,7.3-16.1,16.2-21.5,25.9
|
||||
c-25.8-7.1-53.2-9.1-80-6.8c-3.9,0.3-7.8,0.8-11.7,1.3c-1.5-12.2-4.7-25.3-13.2-34.3c-7.6-8-19.1-9.2-27.6-1.9
|
||||
c-9.2,8-12.2,20.9-12.7,32.6c-0.2,5.8,0.4,11.5,1.5,17.1c-6.1,2.5-12,5.2-17.8,8.3c-24.2,13-43.9,32.7-61.6,53.4
|
||||
c-0.4,0.5-0.7,1-0.9,1.6c-2.4,2.9-4.7,5.8-7,8.8c-10.1-4.9-21.7-9.1-33-7.6c-11.9,1.5-19.8,11.8-16.1,23.6
|
||||
c3.4,11,15.2,18.2,24.7,23.2c0.1,0.1,0.3,0.1,0.4,0.2c-2.2,4.7-4.2,9.6-6,14.4c-8.6,23.3-11.6,48.4-11.3,73.2
|
||||
c0.1,9.7,1,19.3,2.6,28.8c-7.7,2.4-15,5.9-21.2,11.6c-8.7,8-11.5,21.1-4.7,31.1c7.1,10.4,21.2,11.8,32.6,9.9
|
||||
c3.5-0.6,7-1.5,10.4-2.5c8.5,15.1,19.8,28.5,32.4,40.2c12.3,11.4,26.1,21.1,40.6,29.5c-2,4.6-3.9,9.4-5.5,14.2
|
||||
c-3.3,9.7-4.9,22.2,4.6,29.1c10.8,7.9,23.6,1.4,31.8-6.8c4.5-4.5,8.3-9.9,11.7-15.5c12.7,5.3,25.6,9.8,38.9,13.3
|
||||
c21.7,5.8,44.3,8.6,66.8,7.1c3.4,5.7,8,10.6,14.2,14.2c9.8,5.7,23.6,4.7,31.6-3.5c6.7-6.8,8.3-15.4,7.2-24.1
|
||||
c3.9-1.8,7.8-3.8,11.6-5.9c28.3-15.9,51.2-39.8,68.5-67.2c6.8,2.5,13.9,4.3,21.2,5c10.8,1.1,23.6-0.7,28.2-12
|
||||
c4.5-10.9-2.9-23-10-30.9c-4.5-5-9.7-8.9-15.3-12.1c11.8-34.1,15-70.7,8.8-106.2c10.5-3.4,20.9-9.7,26.9-19
|
||||
C497,209.5,495.7,197.9,486,191.6z M440.5,192.4c-8,2.4-16,5.3-23.9,7.9c-10.1-21.4-24.7-40.5-43.3-55.7
|
||||
c-4.8-4-9.9-7.6-15.1-10.8c4.4-7.4,8.7-14.8,12.6-22.5C402,130.2,425.6,159.3,440.5,192.4z M311.9,278.3
|
||||
c-1.8,18.8-16.4,33.3-34,38.6c-17.9,5.3-38,1-51.2-12.4c-11.5-11.7-13.4-27.7-8-41.6c-0.3-4.3,2.3-8.8,4.4-12.2
|
||||
c2.2-3.6,4.8-7.8,8.3-10.6c-0.2-0.4-0.4-0.7-0.7-1c11.8-11.7,34.6-11.8,49-6.8c1.4,0.5,2.8,1.1,4.2,1.7c0.9,2.3,3.7,3.7,6.3,3.4
|
||||
c5.4,3.3,10.2,7.6,14.1,12.5C311,258,312.9,268,311.9,278.3z M382,365c1.8,1.4,3.7,2.8,5.6,4.1c-15.1,18.9-34.4,34.5-56.1,45.3
|
||||
c-4.7-14.6-7.8-29.8-12.1-44.6c-5.3-18.1-11.4-35.9-17.6-53.7c1.8-1.4,3.5-2.9,5.1-4.4C332.7,328.3,357.4,346.5,382,365z
|
||||
M283.7,418.7c0.8,3.5,1.5,7,2.1,10.5c-25.3,4-51.1,1.7-75.3-6.3c3.1-7.9,5.7-16.1,7.8-24.3c6.1-24.9,18.5-47.4,26.2-71.8
|
||||
c3.7,1.1,7.5,1.9,11.3,2.4c4.1,0.5,8.3,0.5,12.5,0.1C270.1,359.6,277.3,389.2,283.7,418.7z M219.7,312.3c-7,7.4-11,17-14.7,26.7
|
||||
c-5.1,13.5-11.4,27-18.2,39.7c-4.6,8.7-10.5,16.7-16.2,24.8c-20.4-13.8-37.1-32.3-48.6-53.7c7.2-2.7,14.6-5.2,21.9-8.1
|
||||
c24.6-9.7,48.1-22.3,73.6-29.6c0.5-0.1,0.9-0.3,1.3-0.6C219.2,311.8,219.4,312.1,219.7,312.3z M215,236.1
|
||||
c-13-7.8-24.9-17.2-37.1-26.1c-5.8-4.2-11.6-8.3-17.6-12.1c-6.6-4.2-13.8-7.4-20.7-11c-0.5-0.3-1-0.5-1.4-0.8
|
||||
c9.9-13.8,21.3-26.5,33.6-37.8c0.2-0.2,0.4-0.4,0.5-0.6c8.1-3.3,16.4-6.6,24.8-9.6c1.5,7.1,3.1,14.2,5.2,21.2
|
||||
c7.2,23.6,17.2,46.3,24.2,70c-3.6,2.8-6.6,6.2-8.9,10.1C217.2,238.1,216.4,236.9,215,236.1z M254,174
|
||||
c-2.6-15.8-5.6-31.6-9.5-47.1c-0.2-0.6-0.3-1.3-0.5-1.9c20.8-3.9,42-4.8,63-0.7c1.9,0.4,3.8,0.8,5.8,1.3c-2,6.1-4,12.1-6.5,18
|
||||
c-3.5,8.7-8.2,16.9-12.5,25.2c-8.5,16.3-16.9,32.7-23.1,50c-0.1,0.2-0.1,0.3-0.2,0.5c-3.3-0.5-6.8-0.8-10.3-0.8
|
||||
C259.3,203.7,256.3,188.7,254,174z M309.2,235.8c-0.8,0.3-1.5,0.8-2,1.4c-2.9-2.8-6.2-5.4-9.6-7.6c15.9-24.8,32.2-49.3,47.5-74.4
|
||||
c2.5-4.2,5.1-8.3,7.7-12.5c23.1,14.7,41.9,36.1,54,61c-12.1,4.1-24.2,8.2-36.5,11.8C349.6,221.4,329.1,227.7,309.2,235.8z
|
||||
M349.3,74.3c2.9-3.1,20.3-20.6,24.4-11.9c1.9,3.9,0.5,9.2-0.4,13.2c-1.5,6.4-3.9,12.5-6.5,18.5c-11.1,26.1-27.6,50.3-42.6,74.3
|
||||
c-11.8,18.9-24.3,37.3-36.3,56.1c-2.2-1-4.4-1.8-6.7-2.5c4.9-13.3,11.1-26.1,17.6-38.7c6.6-12.9,13.9-25.6,19.1-39.1
|
||||
C327.1,120.4,331.3,93.5,349.3,74.3z M325.1,92c-3.6,7.7-6.5,15.8-9.2,23.9c-15.1-3.7-30.8-5.2-46.6-4.4
|
||||
c-9.2,0.5-18.3,1.6-27.4,3.3c-1.6-9.1-2.5-18.3-3.3-27.6C267.2,83.2,297.1,84.4,325.1,92z M199.8,55.9c2.1-3.9,5.8-8.5,10.7-7.9
|
||||
c6,0.7,9.6,8,11.7,12.8c5.9,13.5,5,28.9,6.9,43.2c2.1,15.4,6.1,30.3,9.2,45.4c3.3,16,5.8,32.2,8.1,48.4c1,7.2,2.3,14.4,2.8,21.7
|
||||
c-4.5,0.8-8.9,2.1-13,4c-8.9-29.3-22.2-57.4-28.3-87.5c-1.5-7.4-3.1-14.7-5-21.9c-2-7.6-4.7-15.2-6.1-22.9
|
||||
C194.5,79.4,194.1,66.7,199.8,55.9z M116.4,156.1c0.3-0.3,0.5-0.7,0.6-1c13.3-15.2,27.8-29.5,44.7-40.6c8.2-5.4,17-9.9,26.1-13.7
|
||||
c1.1,4.4,2.4,8.8,3.6,13.2c1.3,4.6,2.3,9.2,3.3,13.8c-6.6,2.3-13.1,4.8-19.6,7.4c-2.8,1.7-5.5,3.6-8.2,5.5
|
||||
c-0.6,0.4-1.2,0.8-1.8,1.2c-3.2,2.8-6.5,5.4-9.9,7.8c-6.1,6.3-12,13-17.6,19.7c-3.1,3.7-6,7.5-8.8,11.5
|
||||
c-7.4-4.3-14.7-8.7-22.2-12.8C109.9,164.1,113.1,160.1,116.4,156.1z M62.2,180.7c-3.3-4.2-4.5-9,0.5-12.3c4.6-3,12-1.2,16.9,0
|
||||
c13.4,3.3,25.7,11.4,37.5,18.4c13,7.7,27.1,13.9,39.8,22c12.8,8.2,24.6,17.9,37,26.7c5.2,3.7,10.6,7.3,16.1,10.6
|
||||
c1.7,1,3.3,1,4.6,0.4c-0.1,0.6,0,1.3,0.1,1.8c-0.7,1-1.4,2.1-2,3.1c-13.4-7.1-27.2-13.3-41-19.5c-13.6-6.1-26.8-13.4-40.5-19.1
|
||||
c-12.3-5.1-25.3-8.5-37.8-13.2C82.3,195.4,69.7,190.2,62.2,180.7z M208.1,261.2c-1.5,4.5-2.4,9.2-2.7,13.9
|
||||
c-14.9,5.6-30.2,9.9-45.4,14.7c-15,4.7-29.7,10.4-44.3,16.1c-2.8,1.1-5.5,2.1-8.4,3.1c-1.1-5.4-1.9-10.9-2.3-16.5
|
||||
c-2.1-25.6,3.3-50.2,13.5-72.8c10.7,4.1,20.8,9.1,31.2,14C169.2,243,189.1,251.1,208.1,261.2z M67.5,263.5
|
||||
c1.7-19.5,7-38.7,15.1-56.5c8.4,3.6,17.2,6.3,25.9,9.1c-7.7,17.3-12.6,35.9-14,54.8c-1,13.8,0,27.6,2.7,41c-1,0.3-2,0.5-3.1,0.7
|
||||
c-7.9,1.7-16,2.5-23.9,4.2c-0.6,0.1-1.1,0.2-1.7,0.4C65.8,299.5,65.9,281.5,67.5,263.5z M50.6,361.1c-5.1-1.3-9.2-5.4-9.7-10.6
|
||||
c-0.6-6.1,4.5-11.3,9-14.5c10.4-7.5,25-9,37.2-11c15.6-2.5,29.6-7.7,44.2-13.5c14.8-5.9,30.1-10.7,45.3-15.3
|
||||
c9.9-3,19.8-6.1,29.6-9.6c0.6,3.2,1.5,6.5,2.8,9.7c0.8,2.1,1.8,4.2,3,6.1c-29.5,8.8-56.5,24.3-85.7,34.1
|
||||
c-13.1,4.4-25.1,10.4-37.5,16.4C77.2,358.5,63.6,364.3,50.6,361.1z M102.4,389.7c-6.4-7.3-12-15.1-16.7-23.4
|
||||
c6.2-2.7,12.3-5.8,18.2-8.8c2.8-1.4,5.6-2.7,8.4-3.9c8.7,16.4,20.1,31.3,33.9,43.8c5.8,5.3,12,10.1,18.6,14.5c-0.7,1-1.3,2-2,3
|
||||
c-3.3,5.2-6.3,10.5-9,16C134.7,419.9,117.1,406.3,102.4,389.7z M187.9,445.3c-5.7,10.6-12.9,25.9-25.3,29.7
|
||||
c-5,1.5-9.2-1.7-9.9-6.5c-0.8-5.9,2.1-12.3,4.1-17.6c5.2-13.5,12.6-26.2,20.5-38.3c3.8-5.7,8.2-11,11.9-16.7
|
||||
c4.3-6.5,8-13.3,11.4-20.3c6.2-12.6,11.5-25.5,16.7-38.5c2.6-6.4,5.7-13.2,10.7-18c2,1.3,4,2.5,6.1,3.5c0,0.1-0.1,0.2-0.1,0.4
|
||||
c-4.2,13.6-10.1,26.4-15.5,39.5c-5.5,13.3-9,26.8-12.9,40.6C201.5,417.8,195.1,431.9,187.9,445.3z M220.7,461
|
||||
c-8-2.5-16-5.4-23.9-8.7c2.1-4,4-7.9,5.9-11.8c1.3-2.6,2.5-5.3,3.7-7.9c20.2,6.9,41.7,10,63,8.9c6.1-0.3,12.2-1,18.2-2
|
||||
c1.4,8.2,2.8,16.4,4.3,24.6c0.5,2.6,1.1,5.1,1.9,7.5C269.4,472.8,244.4,468.5,220.7,461z M332.3,486.3
|
||||
c-10.2,4.7-19.7-2.7-24.9-11.3l-1.1-4.4c-0.4,0-0.7,0.1-1.1,0.1c-0.2-0.4-0.3-0.8-0.5-1.2c-2.3-6.2-2.7-12.8-3.7-19.2
|
||||
c-1.3-8-2.9-15.9-4.5-23.9c-6.4-31.2-14.6-62.1-17.1-94c-0.1-1.7-0.2-3.3-0.3-5c4.5-1.2,8.9-2.9,13.1-5.1
|
||||
c10.1,29.2,18.8,58.6,26.7,88.5c3.9,14.9,10.2,28.9,16.4,42.9C339.7,463.5,345.5,480.2,332.3,486.3z M353.9,455.7
|
||||
c-1.6,0.9-3.3,1.7-5,2.5c-0.1-0.4-0.3-0.8-0.4-1.2c-3.9-11.2-9.2-21.7-13.6-32.7c20.4-9.9,38.9-23.8,54.1-40.7
|
||||
c2.4-2.7,4.8-5.5,7-8.4c8.4,5.8,17.3,11.3,26.6,15.5C405.4,417.6,382.1,440.8,353.9,455.7z M458.5,359.8
|
||||
c6.1,5.2,18.8,20.3,8.5,27c-4.1,2.6-10.6,1.9-15.1,1.3c-6.6-0.8-12.9-2.8-19-5.2c-26.3-10.3-47.9-29.9-70.5-46.3
|
||||
c-16-11.6-32.2-22.8-48.8-33.5c1.3-2,2.5-4.1,3.5-6.2c0.3,0.1,0.6,0.3,1,0.4c26.8,7.1,48.8,25,73.7,36.6
|
||||
C413.5,344,439.6,343.9,458.5,359.8z M322.6,269.2c31.1-6.4,62.5-10.4,93.1-19.6c1.6-0.5,3.2-1,4.8-1.4c4,27.2,0.7,55.3-9.5,80.9
|
||||
c-1.6-0.5-3.1-0.9-4.7-1.4c-14.7-4.7-28-13.3-41.2-21.1c-14-8.2-28.4-15.7-44.1-20C322.4,281,323,275,322.6,269.2z M446.9,340.1
|
||||
c-8.3-3.5-17.2-5.9-26-8.2c9-23,13-47.9,11.2-72.9c-0.3-4.6-0.9-9.2-1.6-13.7c2.4-0.7,4.7-1.3,7.1-1.9c5.8-1.4,11.7-2.1,17.6-3.2
|
||||
C460.9,273.6,457.7,307.8,446.9,340.1z M472.5,221.8c-13.2,8.6-29.2,8.7-44,12.6c-15.4,4.1-30.4,9.1-46,12.6
|
||||
c-15.9,3.5-32,5.9-48,8.8c-4.7,0.8-9.3,1.7-13.9,2.7c-0.2-0.7-0.4-1.3-0.7-2c-1.4-3.9-3.3-7.5-5.6-10.9
|
||||
c29.6-11.9,60.5-19.5,90.7-29.8c15.2-5.1,30.3-10.3,45.7-14.9c8.8-2.6,19.9-5,28.5-0.8C490.5,205.8,478.7,217.8,472.5,221.8z"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 439 B |
@@ -1,383 +0,0 @@
|
||||
/* eslint-disable */
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
|
||||
const tourSteps = [
|
||||
{
|
||||
element: document.getElementById('driver-demo-head'),
|
||||
popover: {
|
||||
title: 'Before we start',
|
||||
description: 'This is just one use-case, make sure to check out the rest of the docs below.',
|
||||
nextBtnText: 'Okay, Start!'
|
||||
},
|
||||
}, {
|
||||
element: '#logo_img',
|
||||
popover: {
|
||||
title: 'Focus Anything',
|
||||
description: 'You can use it to highlight literally anything, images, text, div, span, li etc.',
|
||||
position: 'bottom'
|
||||
},
|
||||
}, {
|
||||
element: '#name_driver',
|
||||
popover: {
|
||||
title: 'Why Driver?',
|
||||
description: 'Because it lets you drive the user across the page',
|
||||
position: 'bottom'
|
||||
}
|
||||
}, {
|
||||
element: '#driver-demo-head',
|
||||
popover: {
|
||||
title: 'Let\'s talk features',
|
||||
description: 'You may leave your mouse and use the <strong>arrow keys</strong> to move next and back or <strong>escape key</strong> anytime to close this',
|
||||
position: 'bottom'
|
||||
}
|
||||
}, {
|
||||
element: '#highlight_feature',
|
||||
popover: {
|
||||
title: 'Highlight Feature',
|
||||
description: 'You may use it to highlight single elements (with or without popover) e.g. like facebook does while creating posts'
|
||||
}
|
||||
}, {
|
||||
element: '#feature_introductions_feature',
|
||||
popover: {
|
||||
title: 'Feature Introductions',
|
||||
description: 'With it\'s powerful API you can use it to make programmatic or user driven feature introductions',
|
||||
position: 'bottom'
|
||||
}
|
||||
}, {
|
||||
element: '#focus_shifters_feature',
|
||||
popover: {
|
||||
title: 'Focus Shifters',
|
||||
description: 'If some element or part of the page needs user\'s interaction, you can just call the highlight method. Driver will take care of driving the user there',
|
||||
position: 'bottom'
|
||||
}
|
||||
}, {
|
||||
element: '#customizable_feature',
|
||||
popover: {
|
||||
title: 'Highly Customizable',
|
||||
description: 'Driver has a powerful API allowing you to customize the experience as much as you can.',
|
||||
position: 'bottom'
|
||||
}
|
||||
}, {
|
||||
element: '#keyboard_feature',
|
||||
popover: {
|
||||
title: 'User Friendly',
|
||||
description: 'Your users can control it with the arrow keys on keyboard, or escape to close it',
|
||||
position: 'bottom'
|
||||
}
|
||||
}, {
|
||||
element: '#free_use_feature',
|
||||
popover: {
|
||||
title: 'MIT License',
|
||||
description: 'I believe in open-source and thus Driver is completely free for both personal or commercial use'
|
||||
}
|
||||
}, {
|
||||
element: '#lightweight_feature',
|
||||
popover: {
|
||||
title: 'Only ~4KB',
|
||||
description: 'Driver is free of bloat and written in Vanilla JS. There is no external dependency at all, thus keeping it smaller in size.'
|
||||
}
|
||||
}, {
|
||||
element: '#examples_section',
|
||||
popover: {
|
||||
title: 'Usage Examples',
|
||||
description: 'Have a look at the usage examples and see how you can use it.'
|
||||
}
|
||||
}, {
|
||||
element: '#driver-demo-head',
|
||||
popover: {
|
||||
title: 'Quick Tour Ends',
|
||||
description: 'This was just a sneak peak, have a look at the API section and examples to learn more!'
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
const animatedTourDriver = new Driver({
|
||||
animate: true,
|
||||
opacity: 0.8,
|
||||
padding: 5,
|
||||
showButtons: true,
|
||||
});
|
||||
|
||||
const boringTourDriver = new Driver({
|
||||
animate: false,
|
||||
opacity: 0.8,
|
||||
padding: 5,
|
||||
showButtons: true
|
||||
});
|
||||
|
||||
boringTourDriver.defineSteps(tourSteps);
|
||||
animatedTourDriver.defineSteps(tourSteps);
|
||||
|
||||
document.querySelector('#animated-tour')
|
||||
.addEventListener('click', () => {
|
||||
if (boringTourDriver.isActivated) {
|
||||
boringTourDriver.reset(true);
|
||||
}
|
||||
|
||||
animatedTourDriver.start();
|
||||
});
|
||||
|
||||
document.querySelector('#boring-tour')
|
||||
.addEventListener('click', () => {
|
||||
if (animatedTourDriver.isActivated) {
|
||||
animatedTourDriver.reset(true);
|
||||
}
|
||||
|
||||
boringTourDriver.start();
|
||||
});
|
||||
|
||||
|
||||
document.querySelectorAll('pre code').forEach((element) => {
|
||||
hljs.highlightBlock(element);
|
||||
});
|
||||
|
||||
|
||||
/////////////////////////////////////////////
|
||||
// First example – highlighting without popover
|
||||
/////////////////////////////////////////////
|
||||
const singleDriverNoPopover = new Driver();
|
||||
document.querySelector('#run-single-element-no-popover')
|
||||
.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
singleDriverNoPopover.highlight('#single-element-no-popover');
|
||||
});
|
||||
|
||||
/////////////////////////////////////////////
|
||||
// Form focus examples
|
||||
/////////////////////////////////////////////
|
||||
const focusDriver = new Driver({ padding: 0 });
|
||||
const inputIds = ['creation-input', 'creation-input-2', 'creation-input-3', 'creation-input-4'];
|
||||
inputIds.forEach(inputId => {
|
||||
// Highlight the section on focus
|
||||
document.getElementById(inputId).addEventListener('focus', () => {
|
||||
focusDriver.highlight(`#${inputId}`);
|
||||
});
|
||||
});
|
||||
|
||||
/////////////////////////////////////////////
|
||||
// Highlighting single element with popover
|
||||
/////////////////////////////////////////////
|
||||
const singleDriverWithPopover = new Driver();
|
||||
document.querySelector('#run-single-element-with-popover')
|
||||
.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
singleDriverWithPopover.highlight({
|
||||
element: '#single-element-with-popover',
|
||||
popover: {
|
||||
title: 'Did you know?',
|
||||
description: 'You can add HTML in title or description also!',
|
||||
position: 'top'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
// Highlighting single element with popover position
|
||||
/////////////////////////////////////////////////////
|
||||
const singleDriverWithPopoverPosition = new Driver();
|
||||
document.querySelector('#run-single-element-with-popover-position')
|
||||
.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
singleDriverWithPopoverPosition.highlight({
|
||||
element: '#single-element-with-popover-position',
|
||||
popover: {
|
||||
title: 'Did you know?',
|
||||
description: 'You can add HTML in title or description also!',
|
||||
position: 'top'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
// Highlighting single element with popover position
|
||||
/////////////////////////////////////////////////////
|
||||
const positionBtnsDriver = new Driver({
|
||||
padding: 0,
|
||||
});
|
||||
|
||||
document.querySelector('#position-btn-left')
|
||||
.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
positionBtnsDriver.highlight({
|
||||
element: '#position-btn-left',
|
||||
popover: {
|
||||
title: 'Did you know?',
|
||||
description: 'You can add HTML in title or description also!',
|
||||
position: 'left'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelector('#position-btn-right')
|
||||
.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
positionBtnsDriver.highlight({
|
||||
element: '#position-btn-right',
|
||||
popover: {
|
||||
title: 'Did you know?',
|
||||
description: 'You can add HTML in title or description also!',
|
||||
position: 'right'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelector('#position-btn-bottom')
|
||||
.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
positionBtnsDriver.highlight({
|
||||
element: '#position-btn-bottom',
|
||||
popover: {
|
||||
title: 'Did you know?',
|
||||
description: 'You can add HTML in title or description also!',
|
||||
position: 'bottom'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelector('#position-btn-top')
|
||||
.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
positionBtnsDriver.highlight({
|
||||
element: '#position-btn-top',
|
||||
popover: {
|
||||
title: 'Did you know?',
|
||||
description: 'You can add HTML in title or description also!',
|
||||
position: 'top'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
// Highlighting single element with popover position
|
||||
/////////////////////////////////////////////////////
|
||||
const htmlDriver = new Driver();
|
||||
|
||||
document.querySelector('#run-single-element-with-popover-html')
|
||||
.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
htmlDriver.highlight({
|
||||
element: '#single-element-with-popover-html',
|
||||
popover: {
|
||||
title: '<em>Tags</em> in title or <u>body</u>',
|
||||
description: 'Body can also have <strong>html tags</strong>!',
|
||||
position: 'top'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
// Without Overlay Example
|
||||
/////////////////////////////////////////////////////
|
||||
const withoutOverlay = new Driver({
|
||||
opacity: 0,
|
||||
padding: 0
|
||||
});
|
||||
|
||||
document.querySelector('#run-element-without-popover')
|
||||
.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
withoutOverlay.highlight({
|
||||
element: '#run-element-without-popover',
|
||||
popover: {
|
||||
title: 'Title for the Popover',
|
||||
description: 'Description for it',
|
||||
position: 'top', // can be `top`, `left`, `right`, `bottom`
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/////////////////////////////////////////////
|
||||
// Single no close demo
|
||||
/////////////////////////////////////////////
|
||||
const singleNoClose = new Driver({
|
||||
allowClose: false,
|
||||
position: 'top'
|
||||
});
|
||||
|
||||
singleNoClose.defineSteps([{
|
||||
element: '#single-element-no-close',
|
||||
popover: {
|
||||
title: 'Uh-huh!',
|
||||
description: 'You cannot close by clicking outside'
|
||||
}
|
||||
}, {
|
||||
element: '#third-element-introduction',
|
||||
popover: {
|
||||
title: 'Title on Popover',
|
||||
description: 'Body of the popover',
|
||||
position: 'top'
|
||||
}
|
||||
}]);
|
||||
|
||||
document.querySelector('#run-single-element-no-close').addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
singleNoClose.start();
|
||||
});
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
// Highlighting single element with popover position
|
||||
/////////////////////////////////////////////////////
|
||||
const featureIntroductionDriver = new Driver();
|
||||
featureIntroductionDriver.defineSteps([
|
||||
{
|
||||
element: '#first-element-introduction',
|
||||
popover: {
|
||||
title: 'Title on Popover',
|
||||
description: 'Body of the popover',
|
||||
position: 'top'
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#second-para-feature-introductions',
|
||||
popover: {
|
||||
title: 'Title on Popover',
|
||||
description: 'Body of the popover',
|
||||
position: 'bottom'
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#third-para-feature-introductions',
|
||||
popover: {
|
||||
title: 'Title on Popover',
|
||||
description: 'Body of the popover',
|
||||
position: 'top'
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#run-multi-element-popovers',
|
||||
popover: {
|
||||
title: 'Title on Popover',
|
||||
description: 'Body of the popover',
|
||||
position: 'top'
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#third-element-introduction',
|
||||
popover: {
|
||||
title: 'Title on Popover',
|
||||
description: 'Body of the popover',
|
||||
position: 'top'
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
||||
document.querySelector('#run-multi-element-popovers')
|
||||
.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
featureIntroductionDriver.start();
|
||||
});
|
||||
|
||||
const newURL = location.href.split('?')[0];
|
||||
if (newURL !== location.href) {
|
||||
window.location = newURL;
|
||||
window.location.href = newURL;
|
||||
}
|
||||
});
|
||||
@@ -1,28 +0,0 @@
|
||||
// Just for the sake of styling page
|
||||
/* eslint-disable */
|
||||
|
||||
/**
|
||||
* Determine if this browser supports emoji.
|
||||
*
|
||||
* Modified from https://gist.github.com/mwunsch/4710561
|
||||
* and probobly originally github's javascript source
|
||||
*/
|
||||
function doesSupportEmoji() {
|
||||
let context;
|
||||
if (!document.createElement('canvas').getContext) return;
|
||||
context = document.createElement('canvas').getContext('2d');
|
||||
if ((typeof context.fillText) !== 'function') {
|
||||
return;
|
||||
}
|
||||
|
||||
let smile = String.fromCharCode(55357) + String.fromCharCode(56835);
|
||||
|
||||
context.textBaseline = "top";
|
||||
context.font = "32px Arial";
|
||||
context.fillText(smile, 0, 0);
|
||||
return context.getImageData(16, 16, 1, 1).data[0] !== 0;
|
||||
}
|
||||
|
||||
if (!doesSupportEmoji()) {
|
||||
twemoji.parse(document.body);
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
$global-guttering: 24px;
|
||||
$global-font-size-h1: 32px;
|
||||
$global-font-size-h2: 24px;
|
||||
$global-font-size-h3: 20px;
|
||||
$global-font-size-h4: 18px;
|
||||
$global-font-size-h5: 16px;
|
||||
$global-font-size-h6: 14px;
|
||||
$heading-font-weight: 700;
|
||||
|
||||
/*=============================================
|
||||
= Generic styling =
|
||||
=============================================*/
|
||||
|
||||
* {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
*, *:before, *:after {
|
||||
box-sizing: border-box
|
||||
}
|
||||
|
||||
html, body {
|
||||
position: relative;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
padding-top: 1px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
font-size: 16px;
|
||||
line-height: 1.4;
|
||||
color: white;
|
||||
background-color: #2E2E30;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 20px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
hr, .hr {
|
||||
display: block;
|
||||
margin: $global-guttering*1.25 0;
|
||||
border: 0;
|
||||
border-bottom: 1px solid #eaeaea;
|
||||
height: 1px;
|
||||
|
||||
&.hr__fancy {
|
||||
background: url("./separator.png") repeat-y;
|
||||
height: 5px;
|
||||
background-size: cover;
|
||||
}
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
margin-top: 0;
|
||||
margin-bottom: $global-guttering/2;
|
||||
font-weight: $heading-font-weight;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
a, a:visited, a:focus {
|
||||
color: #FFFFFF;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
display: block;
|
||||
width: 100%;
|
||||
background-color: #f9f9f9;
|
||||
padding: 12px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 2.5px;
|
||||
font-size: 14px;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
outline: none;
|
||||
margin-bottom: $global-guttering;
|
||||
}
|
||||
|
||||
h1, .h1 {
|
||||
font-size: $global-font-size-h1;
|
||||
}
|
||||
|
||||
h2, .h2 {
|
||||
font-size: $global-font-size-h2;
|
||||
}
|
||||
|
||||
h3, .h3 {
|
||||
font-size: $global-font-size-h3;
|
||||
}
|
||||
|
||||
h4, .h4 {
|
||||
font-size: $global-font-size-h4;
|
||||
}
|
||||
|
||||
h5, .h5 {
|
||||
font-size: $global-font-size-h5;
|
||||
}
|
||||
|
||||
h6, .h6 {
|
||||
font-size: $global-font-size-h6;
|
||||
}
|
||||
|
||||
ul li, ol li {
|
||||
list-style-type: none;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
background: #f5f3f1;
|
||||
padding: 10px;
|
||||
margin-bottom: 35px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.top-20 {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.top-30 {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.zero-bottom {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.zero-top {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/*===== End of Section comment block ======*/
|
||||
@@ -1,118 +0,0 @@
|
||||
@import "./base";
|
||||
@import "../../src/driver";
|
||||
|
||||
img.emoji {
|
||||
height: 1em;
|
||||
width: 1em;
|
||||
margin: 0 .05em 0 .1em;
|
||||
vertical-align: -0.1em;
|
||||
}
|
||||
|
||||
.brand {
|
||||
position: relative;
|
||||
|
||||
img {
|
||||
height: 67px;
|
||||
position: relative;
|
||||
top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.page-wrap {
|
||||
max-width: 550px;
|
||||
margin: 50px auto;
|
||||
background: white;
|
||||
padding: 10px 30px;
|
||||
border-radius: 5px;
|
||||
color: #1f1f1f;
|
||||
|
||||
a, a:visited, a:focus {
|
||||
color: #1f1f1f;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a.btn__dark {
|
||||
text-decoration: none;
|
||||
color: #FFFFFF;
|
||||
background-color: #F44336;
|
||||
padding: 0 18px;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s, color 0.2s;
|
||||
font-weight: 500;
|
||||
width: 195px;
|
||||
font-size: 17px;
|
||||
display: inline-block;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
a.btn__run-demo {
|
||||
text-decoration: none;
|
||||
background: #f54336;
|
||||
padding: 5px;
|
||||
border-radius: 20px;
|
||||
color: white;
|
||||
display: block;
|
||||
margin: 15px 0 10px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
section {
|
||||
margin-bottom: $global-guttering*1.25;
|
||||
}
|
||||
|
||||
.section__header {
|
||||
margin: 10px 0 20px;
|
||||
text-align: center;
|
||||
display: block;
|
||||
padding: 10px;
|
||||
|
||||
h1 {
|
||||
font-size: 55px;
|
||||
|
||||
.emoji {
|
||||
font-size: 45px;
|
||||
top: -4px;
|
||||
margin-right: 3px;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
.tagline {
|
||||
max-width: 450px;
|
||||
margin: 0 auto 25px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.section__examples {
|
||||
.section__example {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
#creation-input {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
input {
|
||||
padding: 10px;
|
||||
background-color: #fafbfc;
|
||||
border: 3px solid #e1e4e8;
|
||||
border-radius: 3px;
|
||||
box-shadow: inset 0 0 10px rgba(27, 31, 35, 0.05);
|
||||
margin-bottom: 0;
|
||||
margin-top: 7px;
|
||||
}
|
||||
}
|
||||
|
||||
.position-btns a {
|
||||
text-decoration: none;
|
||||
font-size: 13px;
|
||||
padding: 10px 15px;
|
||||
background: #f54336;
|
||||
color: white !important;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 6.7 KiB |
@@ -1,2 +0,0 @@
|
||||
div#driver-popover-item{display:none;position:absolute;background:#fff;color:#2d2d2d;margin:0;padding:15px;border-radius:5px;min-width:250px;max-width:300px;box-shadow:0 1px 10px rgba(0,0,0,.4);z-index:1000000000}div#driver-popover-item .driver-popover-tip{border:5px solid #fff;content:"";position:absolute}div#driver-popover-item .driver-popover-tip.bottom{bottom:-10px;border-top-color:#fff;border-right-color:transparent;border-bottom-color:transparent;border-left-color:transparent}div#driver-popover-item .driver-popover-tip.left{left:-10px;top:10px;border-top-color:transparent;border-right-color:#fff;border-bottom-color:transparent;border-left-color:transparent}div#driver-popover-item .driver-popover-tip.right{right:-10px;top:10px;border-top-color:transparent;border-right-color:transparent;border-bottom-color:transparent;border-left-color:#fff}div#driver-popover-item .driver-popover-tip.top{top:-10px;border-top-color:transparent;border-right-color:transparent;border-bottom-color:#fff;border-left-color:transparent}div#driver-popover-item .driver-popover-footer{display:block;clear:both;margin-top:5px}div#driver-popover-item .driver-popover-footer button{display:inline-block;padding:3px 10px;border:1px solid #d4d4d4;text-decoration:none;text-shadow:1px 1px 0 #fff;color:#2d2d2d;font:11px/normal sans-serif;cursor:pointer;outline:0;background-color:#f1f1f1;border-radius:2px;zoom:1;margin:10px 0 0;line-height:1.3}div#driver-popover-item .driver-popover-footer button.driver-disabled{color:gray;cursor:default;pointer-events:none}div#driver-popover-item .driver-popover-footer .driver-close-btn{float:left}div#driver-popover-item .driver-popover-footer .driver-btn-group{float:right}div#driver-popover-item .driver-popover-title{font:19px/normal sans-serif;margin:0 0 5px;font-weight:700;display:block;position:relative;line-height:1.5;zoom:1}div#driver-popover-item .driver-popover-description{margin-bottom:0;font:14px/normal sans-serif;line-height:1.5;color:#2d2d2d;font-weight:400;zoom:1}.driver-stage-no-animation{-webkit-transition:none!important;-moz-transition:none!important;-ms-transition:none!important;-o-transition:none!important;transition:none!important;background:transparent!important;outline:5000px solid rgba(0,0,0,.75)}div#driver-page-overlay{background:#000;position:fixed;bottom:0;right:0;display:block;width:100%;height:100%;zoom:1;filter:alpha(opacity=75);opacity:.75;z-index:100002!important}div#driver-highlighted-element-stage,div#driver-page-overlay{top:0;left:0;-webkit-transition:all .4s;-moz-transition:all .4s;-ms-transition:all .4s;-o-transition:all .4s;transition:all .4s}div#driver-highlighted-element-stage{position:absolute;height:50px;width:300px;background:#fff;z-index:100003!important;display:none;border-radius:2px}.driver-highlighted-element{z-index:100004!important}.driver-position-relative{position:relative!important}.driver-fix-stacking{z-index:auto!important;opacity:1!important;-webkit-transform:none!important;-moz-transform:none!important;-ms-transform:none!important;-o-transform:none!important;transform:none!important;-webkit-filter:none!important;-moz-filter:none!important;-ms-filter:none!important;-o-filter:none!important;filter:none!important;-webkit-perspective:none!important;-moz-perspective:none!important;-ms-perspective:none!important;-o-perspective:none!important;perspective:none!important;-webkit-transform-style:flat!important;-moz-transform-style:flat!important;-ms-transform-style:flat!important;transform-style:flat!important;-webkit-transform-box:border-box!important;-moz-transform-box:border-box!important;-ms-transform-box:border-box!important;-o-transform-box:border-box!important;transform-box:border-box!important;will-change:unset!important}
|
||||
/*# sourceMappingURL=driver.min.css.map*/
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"driver.min.css","sources":[],"mappings":"","sourceRoot":""}
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"driver.min.js","sources":["webpack://Driver/webpack/universalModuleDefinition"],"sourcesContent":["(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"Driver\"] = factory();\n\telse\n\t\troot[\"Driver\"] = factory();\n})(window, function() {\nreturn "],"mappings":"AAAA","sourceRoot":""}
|
||||
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
@@ -1,398 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport"
|
||||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Driver</title>
|
||||
<meta name="description" itemprop="description"
|
||||
content="A light-weight, no-dependency, vanilla JavaScript library to drive the user's focus across the page"/>
|
||||
<link rel="stylesheet" href="./dist/demo/driver-demo.min.css">
|
||||
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/monokai.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="page-wrap">
|
||||
<section class="section__header" id="driver-demo-head">
|
||||
<h1 class="brand">
|
||||
<img id="logo_img" src="./dist/demo/driver.png"/>
|
||||
<span id="name_driver">Driver.js</span>
|
||||
</h1>
|
||||
<p class="text-muted tagline">Light-weight, no-dependency, vanilla JavaScript engine to drive user's focus across
|
||||
the
|
||||
page</p>
|
||||
|
||||
<a id="animated-tour" href="javascript:void(0)" class="btn btn__example btn__dark">With Animation</a>
|
||||
<a id="boring-tour" href="javascript:void(0)" class="btn btn__example btn__dark">Without Animation</a>
|
||||
|
||||
<div class="github-button top-30">
|
||||
<!-- Place this tag where you want the button to render. -->
|
||||
<a class="github-button" href="https://github.com/kamranahmedse/driver.js" data-size="large"
|
||||
data-show-count="true" aria-label="Star kamranahmedse/driver.js on GitHub">Star</a>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
<blockquote>
|
||||
<p>A lightweight (~4kb gzipped) yet powerful JavaScript engine that helps you drive the user's focus on page.</p>
|
||||
<p class="zero-bottom">Some sample use-cases can be creating powerful feature introductions, call-to-action
|
||||
components, focus shifters etc.</p>
|
||||
</blockquote>
|
||||
|
||||
<section class="section__purpose">
|
||||
<h3>What are the features?</h3>
|
||||
<p>Driver is compatible with all the major browsers and can be used for any of your overlay needs. Feature
|
||||
introductions, focus shifters, call-to-action are just a few examples.</p>
|
||||
<ul>
|
||||
<li id="highlight_feature">🔆 <strong>Highlight</strong> any (literally any) item on page</li>
|
||||
<li id="interactions_feature">✋ <strong>Block user interactions</strong></li>
|
||||
<li id="feature_introductions_feature">📣 Create <strong>feature introductions</strong></li>
|
||||
<li id="focus_shifters_feature">👓 Add <strong>focus shifters</strong> for users</li>
|
||||
<li id="customizable_feature">🛠️ Highly customizable – <strong>Use it anywhere</strong> for overlay</li>
|
||||
<li id="keyboard_feature">⌨️ User Friendly – <strong>Controllable by keys</strong></li>
|
||||
<li id="free_use_feature">🆓 <strong>MIT Licensed</strong> – Free for personal and commercial use</li>
|
||||
<li id="lightweight_feature">🕊️ Lightweight – Only <strong>~4kb</strong> when gzipped</li>
|
||||
<li id="major_browsers_feature">🌀 <strong>Consistent behavior</strong> across all major browsers</li>
|
||||
</ul>
|
||||
</section>
|
||||
<hr class="hr__fancy">
|
||||
<section class="section__how">
|
||||
<h3>How does it do that?</h3>
|
||||
<p>In it simplest, it puts the canvas on top of the whole page and then cuts the part that is over the element to be
|
||||
highlighted and provides you several hooks when highlighting, highlighted or un-highlighting elements making it
|
||||
highly customizable.</p>
|
||||
</section>
|
||||
<hr class="hr__fancy">
|
||||
<section class="section__examples">
|
||||
<div id="examples_section">
|
||||
<h3>Can you show some Examples?</h3>
|
||||
<p>Below you find some of the examples and sample use-cases on how you can use it. Run by clicking the
|
||||
<code>RUN</code> button.</p>
|
||||
</div>
|
||||
<div id="single-element-no-popover" class="section__example">
|
||||
<h4>Highlighting a Single Element – Without Popover</h4>
|
||||
<p class="zero-bottom">If all you want is just highlight a single element, you can do that simply by passing the
|
||||
selector</p>
|
||||
<a href="#" class="btn__run-demo" id="run-single-element-no-popover">Show Demo</a>
|
||||
<pre><code class="javascript">const driver = new Driver();
|
||||
driver.highlight('#create-post');
|
||||
</code></pre>
|
||||
</div>
|
||||
|
||||
<hr class="hr__fancy">
|
||||
|
||||
<div class="section__example">
|
||||
<p>A <strong>real world use-case</strong> for this could be highlighting an element when user is interacting with
|
||||
it</p>
|
||||
<pre><code class="javascript">const focusDriver = new Driver();
|
||||
|
||||
// Highlight the section on focus
|
||||
document.getElementById('creation-input')
|
||||
.addEventListener('focus', (e) => {
|
||||
focusDriver.focus('#creation-input');
|
||||
});
|
||||
</code></pre>
|
||||
<p class="top-20">Focus any of the inputs and see how it moves the highlight from one element to the other</p>
|
||||
<div id="creation-forms">
|
||||
<input type="text" id="creation-input" class="form-control" placeholder="Focus any of the inputs">
|
||||
<input type="text" id="creation-input-2" class="form-control" placeholder="Focus any of the inputs">
|
||||
<input type="text" id="creation-input-3" class="form-control" placeholder="Focus any of the inputs">
|
||||
<input type="text" id="creation-input-4" class="form-control" placeholder="Focus any of the inputs">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p>You can also turn off the animation or set the padding around the corner. More on it later.</p>
|
||||
|
||||
<hr class="hr__fancy">
|
||||
|
||||
<div id="single-element-with-popover" class="section__example">
|
||||
<h4>Highlighting a Single Element – With Popover</h4>
|
||||
<p>If you would like to show some details alongside the highlighted element, you can do that easily by specifying
|
||||
title and description</p>
|
||||
<a href="#" class="btn__run-demo" id="run-single-element-with-popover">Show Demo</a>
|
||||
<pre><code class="javascript">const driver = new Driver();
|
||||
driver.highlight({
|
||||
element: '#some-element',
|
||||
popover: {
|
||||
title: 'Title for the Popover',
|
||||
description: 'Description for it',
|
||||
}
|
||||
});
|
||||
</code></pre>
|
||||
<p class="top-20">You can modify the behavior of this popover also by a certain set of options. More on it
|
||||
below.</p>
|
||||
</div>
|
||||
|
||||
<hr class="hr__fancy">
|
||||
|
||||
<div id="single-element-with-popover-position" class="section__example">
|
||||
<h4>Popover Positioning</h4>
|
||||
<p>You can also, change the position of the popover to be either <code>left</code>, <code>top</code>,
|
||||
<code>right</code> or <code>bottom</code>.</p>
|
||||
<a href="#" class="btn__run-demo" id="run-single-element-with-popover-position">Show Demo</a>
|
||||
<pre><code class="javascript">const driver = new Driver();
|
||||
driver.highlight({
|
||||
element: '#some-element',
|
||||
popover: {
|
||||
title: 'Title for the Popover',
|
||||
description: 'Description for it',
|
||||
position: 'left', // can be `top`, `left`, `right`, `bottom`
|
||||
}
|
||||
});
|
||||
</code></pre>
|
||||
</div>
|
||||
<div class="section__example">
|
||||
<div class="top-20 position-btns">
|
||||
<a href="#" id="position-btn-left">On my Left</a>
|
||||
<a href="#" id="position-btn-top">On my Top</a>
|
||||
<a href="#" id="position-btn-bottom">On my Bottom</a>
|
||||
<a href="#" id="position-btn-right">On my Right</a>
|
||||
</div>
|
||||
<p class="top-20">If you don't specify the position or specify it to be <code>auto</code>, it will automatically
|
||||
find the suitable position for the popover and show it</p>
|
||||
</div>
|
||||
<hr class="hr__fancy">
|
||||
<div id="single-element-with-popover-html" class="section__example">
|
||||
<h4>HTML in Popovers</h4>
|
||||
<p>You can also specify HTML in the body or the title of the popovers. Here is an example:</p>
|
||||
<a href="#" class="btn__run-demo" id="run-single-element-with-popover-html">Show Demo</a>
|
||||
<pre><code class="javascript">const driver = new Driver();
|
||||
driver.highlight({
|
||||
element: '#some-element',
|
||||
popover: {
|
||||
title: '<em>An italicized title</em>',
|
||||
description: 'Description may also contain <strong>HTML</strong>'
|
||||
}
|
||||
});
|
||||
</code></pre>
|
||||
</div>
|
||||
|
||||
<p class="top-20">And of course it can be any valid HTML.</p>
|
||||
|
||||
<hr class="hr__fancy">
|
||||
<div id="single-element-no-close" class="section__example">
|
||||
<h4>Disable Close on Outside Click</h4>
|
||||
<p>By default, driver.js gets reset if user clicks outside the highlighted element, you can disable this:</p>
|
||||
<a href="#" class="btn__run-demo" id="run-single-element-no-close">Show Demo</a>
|
||||
<pre><code class="javascript">const driver = new Driver({
|
||||
allowClose: false,
|
||||
});
|
||||
|
||||
driver.highlight({
|
||||
element: '#some-element',
|
||||
popover: {
|
||||
title: '<em>An italicized title</em>',
|
||||
description: 'Description may also contain <strong>HTML</strong>'
|
||||
}
|
||||
});
|
||||
</code></pre>
|
||||
<p class="top-20">
|
||||
If you use this option, for multi-step driver, it would close after you are done with the popover or you can close it programmatically. For single element popover, you need to close it properly, otherwise it won't be closed
|
||||
</p>
|
||||
<pre><code class="javascript">driver.reset()</code>
|
||||
</div>
|
||||
|
||||
<hr class="hr__fancy">
|
||||
<div id="third-element-introduction" class="section__example">
|
||||
<h4 id="first-element-introduction">Creating Feature Introductions</h4>
|
||||
<p id="second-para-feature-introductions">You can also make powerful feature introductions to guide the users
|
||||
about the features. Just provide an array of steps where each step specifies an element to highlight.</p>
|
||||
<p id="third-para-feature-introductions">Below is just a quick example showing you how to combine the steps in
|
||||
introduction.</p>
|
||||
<a href="#" class="btn__run-demo" id="run-multi-element-popovers">Show Demo</a>
|
||||
</div>
|
||||
<pre><code class="javascript">const driver = new Driver();
|
||||
// Define the steps for introduction
|
||||
driver.defineSteps([
|
||||
{
|
||||
element: '#first-element-introduction',
|
||||
popover: {
|
||||
title: 'Title on Popover',
|
||||
description: 'Body of the popover',
|
||||
position: 'left'
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#second-element-introduction',
|
||||
popover: {
|
||||
title: 'Title on Popover',
|
||||
description: 'Body of the popover',
|
||||
position: 'top'
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#third-element-introduction',
|
||||
popover: {
|
||||
title: 'Title on Popover',
|
||||
description: 'Body of the popover',
|
||||
position: 'right'
|
||||
}
|
||||
},
|
||||
]);
|
||||
// Start the introduction
|
||||
driver.start();
|
||||
</code></pre>
|
||||
<p class="top-20">This is just a quick example for the feature introduction. For a richer one, please have a look at
|
||||
the
|
||||
<a href="#" class="btn__example">"Quick Tour"</a></p>
|
||||
|
||||
<p>You may also turn off the footer buttons in popover, in which case user can control the popover using the arrows
|
||||
keys on keyboard. Or you may control it using the methods provided by Driver.</p>
|
||||
|
||||
<hr class="hr__fancy">
|
||||
|
||||
<div id="element-without-popover" class="section__example">
|
||||
<h4>Without Overlay</h4>
|
||||
<p>You can create feature introductions and do everything listed above without overlays also. All you have to do
|
||||
is just set the opacity to `0`.</p>
|
||||
<a href="#" class="btn__run-demo" id="run-element-without-popover">Show Demo</a>
|
||||
<pre><code class="javascript">const driver = new Driver({
|
||||
opacity: 0,
|
||||
});
|
||||
|
||||
driver.highlight({
|
||||
element: '#run-element-without-popover',
|
||||
popover: {
|
||||
title: 'Title for the Popover',
|
||||
description: 'Description for it',
|
||||
position: 'top', // can be `top`, `left`, `right`, `bottom`
|
||||
}
|
||||
});
|
||||
</code></pre>
|
||||
<p class="top-20">..and you can do the same for the feature introductions</p>
|
||||
</div>
|
||||
|
||||
<hr class="hr__fancy">
|
||||
|
||||
<div class="section__example">
|
||||
<div id="api_section">
|
||||
<h3>..and much much more</h3>
|
||||
<p>Driver comes with many options that you can manipulate to make driver behave as you may like</p>
|
||||
</div>
|
||||
<h4>Driver Definition</h4>
|
||||
<p>Here are the options that Driver understands</p>
|
||||
<pre><code class="javascript">const driver = new Driver({
|
||||
animate: true, // Animate while changing highlighted element
|
||||
opacity: 0.75, // Background opacity (0 means only popovers and without overlay)
|
||||
padding: 10, // Distance of element from around the edges
|
||||
allowClose: true, // Whether clicking on overlay should close or not
|
||||
overlayClickNext: false, // Should it move to next step on overlay click
|
||||
doneBtnText: 'Done', // Text on the final button
|
||||
closeBtnText: 'Close', // Text on the close button for this step
|
||||
nextBtnText: 'Next', // Next button text for this step
|
||||
prevBtnText: 'Previous', // Previous button text for this step
|
||||
showButtons: false, // Do not show control buttons in footer
|
||||
keyboardControl: true, // Allow controlling through keyboard (escape to close, arrow keys to move)
|
||||
scrollIntoViewOptions: {}, // We use `scrollIntoView()` when possible, pass here the options for it if you want any
|
||||
onHighlightStarted: (Element) {}, // Called when element is about to be highlighted
|
||||
onHighlighted: (Element) {}, // Called when element is fully highlighted
|
||||
onDeselected: (Element) {}, // Called when element has been deselected
|
||||
onReset: (Element) {}, // Called when overlay is about to be cleared
|
||||
onNext: (Element) => {}, // Called when moving to next step on any step
|
||||
onPrevious: (Element) => {}, // Called when moving to next step on any step
|
||||
});
|
||||
</code></pre>
|
||||
</div>
|
||||
<p class="top-20">Note that all the button options that you provide in the driver definition can be overridden for a
|
||||
specific step by giving them in the step definition</p>
|
||||
<div class="section__example">
|
||||
<h4>Step Definition</h4>
|
||||
<p>Here are the set of options that you can pass in each step i.e. an item in array of steps or the object that
|
||||
you pass to <code>highlight</code> method</p>
|
||||
<pre><code class="javascript">const stepDefinition = {
|
||||
element: '#some-item', // Query selector string or Node to be highlighted
|
||||
popover: { // There will be no popover if empty or not given
|
||||
title: 'Title', // Title on the popover
|
||||
description: 'Description', // Body of the popover
|
||||
showButtons: false, // Do not show control buttons in footer
|
||||
closeBtnText: 'Close', // Text on the close button for this step
|
||||
nextBtnText: 'Next', // Next button text for this step
|
||||
prevBtnText: 'Previous', // Previous button text for this step
|
||||
}
|
||||
};
|
||||
</code></pre>
|
||||
</div>
|
||||
<div class="section__example">
|
||||
<h4>API Methods</h4>
|
||||
<p>Below are the set of methods that are available to you</p>
|
||||
<pre><code class="javascript">const driver = new Driver(driverOptions);
|
||||
|
||||
const isActivated = driver.isActivated; // Checks if the driver is active or not
|
||||
driver.moveNext(); // Moves to next step in the steps list
|
||||
driver.movePrevious(); // Moves to previous step in the steps list
|
||||
driver.start(stepNumber = 0); // Starts driving through the defined steps
|
||||
driver.highlight(string|stepDefinition); // highlights the element using query selector or the step definition
|
||||
driver.reset(); // Resets the overlay and clears the screen
|
||||
driver.hasHighlightedElement(); // Checks if there is any highlighted element
|
||||
driver.hasNextStep(); // Checks if there is next step to move to
|
||||
driver.hasPreviousStep(); // Checks if there is previous step to move to
|
||||
|
||||
// Prevents the current move. Useful in `onNext` or `onPrevious` if you want to
|
||||
// perform some asynchronous task and manually move to next step
|
||||
driver.preventMove();
|
||||
|
||||
// Gets the currently highlighted element on screen
|
||||
const activeElement = driver.getHighlightedElement();
|
||||
const lastActiveElement = driver.getLastHighlightedElement();
|
||||
activeElement.getCalculatedPosition(); // Gets screen co-ordinates of the active element
|
||||
activeElement.hidePopover(); // Hide the popover
|
||||
activeElement.showPopover(); // Show the popover
|
||||
|
||||
activeElement.getNode(); // Gets the DOM Element behind this element
|
||||
</code></pre>
|
||||
</div>
|
||||
<p class="top-20">You can use a variety of options to achieve whatever you may want. I have some plans on improving
|
||||
it further, make sure to keep an eye on the <a href="https://github.com/kamranahmedse/driver.js" target="_blank">GitHub
|
||||
page</a></p>
|
||||
</section>
|
||||
<hr class="hr__fancy">
|
||||
<div class="section__example">
|
||||
<h4>Contributing</h4>
|
||||
<p>You can find the contribution instructions on the <a href="https://github.com/kamranahmedse/driver.js"
|
||||
target="_blank">GitHub page</a>. Feel free to submit an
|
||||
issue, create a pull request or spread the word</p>
|
||||
</div>
|
||||
<hr class="hr__fancy">
|
||||
<div class="section__example">
|
||||
<p>A product by <a href="http://twitter.com/kamranahmedse" target="_blank">Kamran</a> produced out of boredom and
|
||||
frustration after he gave up on trying to find a perfect solution to integrate journey introduction
|
||||
and overlays.</p>
|
||||
<a href="https://twitter.com/kamranahmedse?ref_src=twsrc%5Etfw"
|
||||
class="twitter-follow-button mr-3"
|
||||
data-show-screen-name="false"
|
||||
data-size="large"
|
||||
target="_blank"
|
||||
data-show-count="true">Follow @kamranahmedse</a>
|
||||
|
||||
<a class="github-button" href="https://github.com/kamranahmedse"
|
||||
data-size="large"
|
||||
target="_blank"
|
||||
data-show-count="true"
|
||||
aria-label="Follow @kamranahmedse on GitHub">Follow</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
|
||||
<script async defer src="//buttons.github.io/buttons.js"></script>
|
||||
<script src="//twemoji.maxcdn.com/2/twemoji.min.js?2.5"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js"></script>
|
||||
|
||||
<script src="./dist/demo/driver-demo.min.js"></script>
|
||||
|
||||
<script>
|
||||
(function (i, s, o, g, r, a, m) {
|
||||
i['GoogleAnalyticsObject'] = r;
|
||||
i[r] = i[r] || function () {
|
||||
(i[r].q = i[r].q || []).push(arguments);
|
||||
}, i[r].l = 1 * new Date();
|
||||
a = s.createElement(o),
|
||||
m = s.getElementsByTagName(o)[0];
|
||||
a.async = 1;
|
||||
a.src = g;
|
||||
m.parentNode.insertBefore(a, m);
|
||||
})(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');
|
||||
ga('create', 'UA-58155965-1', 'auto');
|
||||
ga('send', 'pageview');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,21 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018 Kamran Ahmed
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,46 +0,0 @@
|
||||
{
|
||||
"name": "driver.js",
|
||||
"version": "0.6.1",
|
||||
"description": "A light-weight, no-dependency, vanilla JavaScript library to drive the user's focus across the page",
|
||||
"main": "dist/driver.min.js",
|
||||
"types": "types/index.d.ts",
|
||||
"scripts": {
|
||||
"start": "node server.js",
|
||||
"build-demo": "NODE_ENV=production webpack --config webpack.config.demo.js",
|
||||
"build": "webpack --config webpack.config.prod.js"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/kamranahmedse/driver.js/issues"
|
||||
},
|
||||
"homepage": "https://github.com/kamranahmedse/driver.js#readme",
|
||||
"repository": "https://github.com/kamranahmedse/driver.js",
|
||||
"author": "Kamran Ahmed <kamranahmed.se@gmail.com>",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"babel-core": "^6.26.0",
|
||||
"babel-eslint": "^8.2.2",
|
||||
"babel-loader": "^7.1.3",
|
||||
"babel-plugin-add-module-exports": "^0.2.1",
|
||||
"babel-plugin-transform-object-rest-spread": "^6.26.0",
|
||||
"babel-preset-env": "^1.6.1",
|
||||
"copy-webpack-plugin": "^4.5.1",
|
||||
"css-loader": "^0.28.10",
|
||||
"eslint": "^4.18.2",
|
||||
"eslint-config-airbnb-base": "^12.1.0",
|
||||
"eslint-loader": "^2.0.0",
|
||||
"eslint-plugin-import": "^2.9.0",
|
||||
"eslint-plugin-node": "^6.0.1",
|
||||
"extract-loader": "^1.0.2",
|
||||
"extract-text-webpack-plugin": "next",
|
||||
"file-loader": "^1.1.11",
|
||||
"node-sass": "^4.7.2",
|
||||
"opn": "^5.2.0",
|
||||
"postcss-loader": "^2.1.1",
|
||||
"sass-loader": "^6.0.7",
|
||||
"style-loader": "^0.20.2",
|
||||
"webpack": "^4.0.1",
|
||||
"webpack-cli": "^2.0.10",
|
||||
"webpack-dev-server": "^3.1.0"
|
||||
},
|
||||
"dependencies": {}
|
||||
}
|
||||
@@ -1,349 +0,0 @@
|
||||
<h1 align="center"><img height="150" src="./demo/images/driver.png" /><br> Driver.js</h1>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/kamranahmedse/driver.js/blob/master/license">
|
||||
<img src="https://img.shields.io/badge/License-MIT-yellow.svg" />
|
||||
</a>
|
||||
<a href="https://npmjs.org/package/driver.js">
|
||||
<img src="https://badge.fury.io/js/driver.js.svg" alt="version" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<b>Powerful yet light-weight, vanilla JavaScript engine to drive the user's focus across the page</b></br>
|
||||
<sub>Only ~4kb, no external dependency, supports all major browsers and highly customizable <sub>
|
||||
</p>
|
||||
|
||||
<br />
|
||||
|
||||
* **Simple**: is simple to use and has no external dependency at all
|
||||
* **Light-weight**: ~4kb in size, vanilla JavaScript and no external dependency
|
||||
* **Highly customizable**: has a powerful API and can be used however you want
|
||||
* **Highlight anything**: highlight any (literally any) element on page
|
||||
* **Feature introductions**: create powerful feature introductions for your web applications
|
||||
* **Focus shifters**: add focus shifters for users
|
||||
* **User friendly**: Everything is controllable by keyboard
|
||||
* **Consistent behavior**: usable across all browsers (including in-famous IE)
|
||||
* **MIT Licensed**: free for personal and commercial use
|
||||
|
||||

|
||||
|
||||
For Usage and Examples, [have a look at demo](http://kamranahmed.info/driver.js)
|
||||
|
||||
## So, yet another tour library?
|
||||
|
||||
**No**, it is not. **Tours are just one of the many use-cases**. Driver.js can be used wherever you need some sort of overlay for the page; some common usecases could be: e.g. dimming the background when user is interacting with some component i.e. [the way Facebook does](https://i.imgur.com/Q3PzaKk.png) when you try to create a post, using it as a focus shifter to bring user's attention to some component on page, or using it to simulate those "Turn off the Lights" widgets that you might have seen on video players online, etc.
|
||||
|
||||
Driver.js is written in Vanilla JS, has zero dependencies and is highly customizable. It has several options allowing you to manipulate how it behaves and also **provides you the hooks** to manipulate the elements as they are highlighted, about to be highlighted, or deselected.
|
||||
|
||||
## Installation
|
||||
|
||||
You can install it using `yarn` or `npm`, whatever you prefer.
|
||||
|
||||
```bash
|
||||
yarn add driver.js
|
||||
npm install driver.js
|
||||
```
|
||||
Or include it using CDN. If you want a specific version, put it as `driver.js@0.5` in the name
|
||||
```html
|
||||
<script src="https://unpkg.com/driver.js/dist/driver.min.js"></script>
|
||||
<script src="https://unpkg.com/driver.js/dist/driver.min.css"></script>
|
||||
```
|
||||
|
||||
Or grab the code from `dist` directory and include it directly.
|
||||
|
||||
```html
|
||||
<link rel="stylesheet" href="/dist/driver.min.css">
|
||||
<script src="/dist/driver.min.js"></script>
|
||||
```
|
||||
|
||||

|
||||
|
||||
## Usage and Demo
|
||||
|
||||
Demos and many more usage examples can be found [in the docs page](http://kamranahmed.info/driver.js).
|
||||
|
||||
### Highlighting Single Element – [Demo](http://kamranahmed.info/driver.js#single-element-no-popover)
|
||||
|
||||
You can highlight a single element by simply passing the selector.
|
||||
|
||||
```javascript
|
||||
const driver = new Driver();
|
||||
driver.highlight('#create-post');
|
||||
```
|
||||
A real world usage example for this is: using it to dim the background and highlight the required element e.g. the way Facebook does it when creating a post.
|
||||
|
||||
### Highlight and Popover – [Demo](http://kamranahmed.info/driver.js#single-element-with-popover)
|
||||
|
||||
You can show additional details beside the highlighted element using the popover.
|
||||
|
||||
```javascript
|
||||
const driver = new Driver();
|
||||
driver.highlight({
|
||||
element: '#some-element',
|
||||
popover: {
|
||||
title: 'Title for the Popover',
|
||||
description: 'Description for it',
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Also, `title` and `description` can have HTML as well.
|
||||
|
||||
### Positioning the Popover – [Demo](http://kamranahmed.info/driver.js#single-element-with-popover-position)
|
||||
|
||||
By default, driver automatically finds the suitable position for the popover and displays it. You can override it using `position` property.
|
||||
|
||||
```javascript
|
||||
const driver = new Driver();
|
||||
driver.highlight({
|
||||
element: '#some-element',
|
||||
popover: {
|
||||
title: 'Title for the Popover',
|
||||
description: 'Description for it',
|
||||
position: 'left', // can be `top`, `left`, `right`, `bottom`
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Creating Feature Introductions – [Demo](http://kamranahmed.info/driver.js)
|
||||
|
||||
Feature introductions are helpful when onboarding new users and giving them an idea about different parts of the application; you can create them seemlessly with Driver. Define the steps and call the `start` when you want to start presenting. User will be able to control the steps using the keyboard or using the buttons on popovers.
|
||||
|
||||
```javascript
|
||||
const driver = new Driver();
|
||||
|
||||
// Define the steps for introduction
|
||||
driver.defineSteps([
|
||||
{
|
||||
element: '#first-element-introduction',
|
||||
popover: {
|
||||
title: 'Title on Popover',
|
||||
description: 'Body of the popover',
|
||||
position: 'left'
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#second-element-introduction',
|
||||
popover: {
|
||||
title: 'Title on Popover',
|
||||
description: 'Body of the popover',
|
||||
position: 'top'
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#third-element-introduction',
|
||||
popover: {
|
||||
title: 'Title on Popover',
|
||||
description: 'Body of the popover',
|
||||
position: 'right'
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
||||
// Start the introduction
|
||||
driver.start();
|
||||
```
|
||||
You can also hide the buttons and control the introductions programmatically by using the API methods listed below.
|
||||
|
||||

|
||||
|
||||
### Asynchronous Actions – [Demo](http://kamranahmed.info/driver.js)
|
||||
|
||||
For any asynchronous actions between the transition steps, you may delay the execution till the action completes. All you have to do is stop the transition using `driver.preventMove()` in your `onNext` or `onPrevious` callbacks and initiate it manually using `driver.moveNext()`. Here is a sample implementation where it will stop at the second step for four seconds and then move on to the next step.
|
||||
|
||||
```javascript
|
||||
const driver = new Driver();
|
||||
|
||||
// Define the steps for introduction
|
||||
driver.defineSteps([
|
||||
{
|
||||
element: '#first-element-introduction',
|
||||
popover: {
|
||||
title: 'Title on Popover',
|
||||
description: 'Body of the popover',
|
||||
position: 'left'
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#second-element-introduction',
|
||||
popover: {
|
||||
title: 'Title on Popover',
|
||||
description: 'Body of the popover',
|
||||
position: 'top'
|
||||
},
|
||||
onNext: () => {
|
||||
// Prevent moving to the next step
|
||||
driver.preventMove();
|
||||
|
||||
// Perform some action or create the element to move to
|
||||
// And then move to that element
|
||||
setTimeout(() => {
|
||||
driver.moveNext();
|
||||
}, 4000);
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#third-element-introduction',
|
||||
popover: {
|
||||
title: 'Title on Popover',
|
||||
description: 'Body of the popover',
|
||||
position: 'right'
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
||||
// Start the introduction
|
||||
driver.start();
|
||||
```
|
||||
You can also hide the buttons and control the introductions programmatically by using the API methods listed below.
|
||||
|
||||

|
||||
|
||||
## API
|
||||
|
||||
Driver comes with several options that you can manipulate to make Driver behave as you like
|
||||
|
||||
### Driver Definition
|
||||
|
||||
Here are the options that Driver understands:
|
||||
|
||||
```javascript
|
||||
const driver = new Driver({
|
||||
animate: true, // Whether to animate or not
|
||||
opacity: 0.75, // Background opacity (0 means only popovers and without overlay)
|
||||
padding: 10, // Distance of element from around the edges
|
||||
allowClose: true, // Whether the click on overlay should close or not
|
||||
overlayClickNext: false, // Whether the click on overlay should move next
|
||||
doneBtnText: 'Done', // Text on the final button
|
||||
closeBtnText: 'Close', // Text on the close button for this step
|
||||
stageBackground: '#ffffff', // Background color for the staged behind highlighted element
|
||||
nextBtnText: 'Next', // Next button text for this step
|
||||
prevBtnText: 'Previous', // Previous button text for this step
|
||||
showButtons: false, // Do not show control buttons in footer
|
||||
keyboardControl: true, // Allow controlling through keyboard (escape to close, arrow keys to move)
|
||||
scrollIntoViewOptions: {}, // We use `scrollIntoView()` when possible, pass here the options for it if you want any
|
||||
onHighlightStarted: (Element) => {}, // Called when element is about to be highlighted
|
||||
onHighlighted: (Element) => {}, // Called when element is fully highlighted
|
||||
onDeselected: (Element) => {}, // Called when element has been deselected
|
||||
onReset: (Element) => {}, // Called when overlay is about to be cleared
|
||||
onNext: (Element) => {}, // Called when moving to next step on any step
|
||||
onPrevious: (Element) => {}, // Called when moving to next step on any step
|
||||
});
|
||||
```
|
||||
Note that all the button options that you provide in the driver definition can be overridden for a specific step by giving them in the step definition
|
||||
|
||||
### Step Definition
|
||||
|
||||
Here are the set of options that you can pass while defining steps `defineSteps` or the object that you pass to `highlight` method:
|
||||
|
||||
```javascript
|
||||
const stepDefinition = {
|
||||
element: '#some-item', // Query selector string or Node to be highlighted
|
||||
stageBackground: '#ffffff', // This will override the one set in driver
|
||||
popover: { // There will be no popover if empty or not given
|
||||
title: 'Title', // Title on the popover
|
||||
description: 'Description', // Body of the popover
|
||||
showButtons: false, // Do not show control buttons in footer
|
||||
doneBtnText: 'Done', // Text on the last button
|
||||
closeBtnText: 'Close', // Text on the close button
|
||||
nextBtnText: 'Next', // Next button text
|
||||
prevBtnText: 'Previous', // Previous button text
|
||||
},
|
||||
onNext: () => {}, // Called when moving to next step from current step
|
||||
onPrevious: () => {}, // Called when moving to next step from current step
|
||||
};
|
||||
```
|
||||
|
||||
For example, here is how it would look when highlighting a single element:
|
||||
|
||||
```javascript
|
||||
const driver = new Driver(driverOptions);
|
||||
driver.highlight(stepDefinition);
|
||||
```
|
||||
|
||||
And this is how it would look when creating a step by step guide:
|
||||
|
||||
```javascript
|
||||
const driver = new Driver(driverOptions);
|
||||
driver.defineSteps([
|
||||
stepDefinition1,
|
||||
stepDefinition2,
|
||||
stepDefinition3,
|
||||
stepDefinition4,
|
||||
]);
|
||||
```
|
||||
|
||||
### API Methods
|
||||
|
||||
Below are the set of methods that are available:
|
||||
|
||||
```javascript
|
||||
const driver = new Driver(driverOptions);
|
||||
|
||||
// Checks if the driver is active or not
|
||||
if (driver.isActivated) {
|
||||
console.log('Driver is active');
|
||||
}
|
||||
|
||||
// In case of the steps guide, you can call below methods
|
||||
driver.defineSteps([ stepDefinition1, stepDefinition2, stepDefinition3 ]);
|
||||
driver.start(stepNumber = 0); // Starts driving through the defined steps
|
||||
driver.moveNext(); // Moves to next step in the steps list
|
||||
driver.movePrevious(); // Moves to previous step in the steps list
|
||||
driver.hasNextStep(); // Checks if there is next step to move to
|
||||
driver.hasPreviousStep(); // Checks if there is previous step to move to
|
||||
|
||||
// Prevents the current move. Useful in `onNext` or `onPrevious` if you want to
|
||||
// perform some asynchronous task and manually move to next step
|
||||
driver.preventMove();
|
||||
|
||||
// Highlights the element using query selector or the step definition
|
||||
driver.highlight(string|stepDefinition);
|
||||
|
||||
// Resets the overlay and clears the screen
|
||||
driver.reset();
|
||||
// Additionally you can pass a boolean parameter
|
||||
// to clear immediately and not do the animations etc
|
||||
// Could be useful when you, let's say, want to run
|
||||
// a different instance of driver while one was running
|
||||
driver.reset(clearImmediately = false);
|
||||
|
||||
// Checks if there is any highlighted element
|
||||
if(driver.hasHighlightedElement()) {
|
||||
console.log('There is an element highlighted');
|
||||
}
|
||||
|
||||
// Gets the currently highlighted element on screen
|
||||
// It would be an instance of `/src/core/element.js`
|
||||
const activeElement = driver.getHighlightedElement();
|
||||
|
||||
// Gets the last highlighted element, would be an instance of `/src/core/element.js`
|
||||
const lastActiveElement = driver.getLastHighlightedElement();
|
||||
|
||||
activeElement.getCalculatedPosition(); // Gets screen co-ordinates of the active element
|
||||
activeElement.hidePopover(); // Hide the popover
|
||||
activeElement.showPopover(); // Show the popover
|
||||
|
||||
activeElement.getNode(); // Gets the DOM Element behind this element
|
||||
```
|
||||
|
||||
**Note –** Do not forget to add `e.stopPropagation()` to the `click` binding that triggers driver.
|
||||
|
||||

|
||||
|
||||
## Contributions
|
||||
|
||||
Feel free to submit pull requests, create issues or spread the word.
|
||||
|
||||
## Sponsored By
|
||||
|
||||
Thanks to [BrowserStack](https://browserstack.com) for sponsoring the compatibility testing needs.
|
||||
|
||||
[](https://www.browserstack.com)
|
||||
|
||||
## License
|
||||
|
||||
MIT © [Kamran Ahmed](https://twitter.com/kamranahmedse)
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 6.7 KiB |
@@ -1,20 +0,0 @@
|
||||
const webpack = require('webpack');
|
||||
const WebpackDevServer = require('webpack-dev-server');
|
||||
const opn = require('opn');
|
||||
|
||||
const config = require('./webpack.config.demo');
|
||||
|
||||
const PORT = 3000;
|
||||
const HOST = 'localhost';
|
||||
const URL = `http://${HOST}:${PORT}`;
|
||||
|
||||
new WebpackDevServer(webpack(config), {
|
||||
publicPath: config.output.publicPath,
|
||||
}).listen(PORT, HOST, (error, result) => {
|
||||
if (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
opn(URL);
|
||||
console.log(`Listening at ${URL}`);
|
||||
});
|
||||
@@ -1,50 +0,0 @@
|
||||
export const OVERLAY_OPACITY = 0.75;
|
||||
export const OVERLAY_PADDING = 10;
|
||||
|
||||
export const SHOULD_ANIMATE_OVERLAY = true;
|
||||
export const SHOULD_OUTSIDE_CLICK_CLOSE = true;
|
||||
export const ALLOW_KEYBOARD_CONTROL = true;
|
||||
export const SHOULD_OUTSIDE_CLICK_NEXT = false;
|
||||
|
||||
export const ESC_KEY_CODE = 27;
|
||||
export const LEFT_KEY_CODE = 37;
|
||||
export const RIGHT_KEY_CODE = 39;
|
||||
|
||||
export const ID_OVERLAY = 'driver-page-overlay';
|
||||
export const ID_STAGE = 'driver-highlighted-element-stage';
|
||||
export const ID_POPOVER = 'driver-popover-item';
|
||||
|
||||
export const CLASS_DRIVER_HIGHLIGHTED_ELEMENT = 'driver-highlighted-element';
|
||||
export const CLASS_POSITION_RELATIVE = 'driver-position-relative';
|
||||
export const CLASS_FIX_STACKING_CONTEXT = 'driver-fix-stacking';
|
||||
|
||||
export const CLASS_STAGE_NO_ANIMATION = 'driver-stage-no-animation';
|
||||
export const CLASS_POPOVER_TIP = 'driver-popover-tip';
|
||||
export const CLASS_POPOVER_TITLE = 'driver-popover-title';
|
||||
export const CLASS_POPOVER_DESCRIPTION = 'driver-popover-description';
|
||||
export const CLASS_POPOVER_FOOTER = 'driver-popover-footer';
|
||||
export const CLASS_CLOSE_BTN = 'driver-close-btn';
|
||||
export const CLASS_NEXT_STEP_BTN = 'driver-next-btn';
|
||||
export const CLASS_PREV_STEP_BTN = 'driver-prev-btn';
|
||||
export const CLASS_BTN_DISABLED = 'driver-disabled';
|
||||
|
||||
// It must match the one set in the animations in CSS file
|
||||
export const ANIMATION_DURATION_MS = 400;
|
||||
|
||||
// language=HTML
|
||||
export const POPOVER_HTML = `
|
||||
<div id="${ID_POPOVER}">
|
||||
<div class="${CLASS_POPOVER_TIP}"></div>
|
||||
<div class="${CLASS_POPOVER_TITLE}">Popover Title</div>
|
||||
<div class="${CLASS_POPOVER_DESCRIPTION}">Popover Description</div>
|
||||
<div class="${CLASS_POPOVER_FOOTER}">
|
||||
<button class="${CLASS_CLOSE_BTN}">Close</button>
|
||||
<span class="driver-btn-group">
|
||||
<button class="${CLASS_PREV_STEP_BTN}">← Previous</button>
|
||||
<button class="${CLASS_NEXT_STEP_BTN}">Next →</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
export const OVERLAY_HTML = `<div id="${ID_OVERLAY}"></div>`;
|
||||
export const STAGE_HTML = `<div id="${ID_STAGE}"></div>`;
|
||||
@@ -1,57 +0,0 @@
|
||||
/**
|
||||
* Turn a string into a node
|
||||
* @param {String} htmlString to convert
|
||||
* @return {Node} Converted node element
|
||||
*/
|
||||
// eslint-disable-next-line
|
||||
export const createNodeFromString = (htmlString) => {
|
||||
const div = document.createElement('div');
|
||||
div.innerHTML = htmlString.trim();
|
||||
|
||||
// Change this to div.childNodes to support multiple top-level nodes
|
||||
return div.firstChild;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the CSS property from the given element
|
||||
* @param {HTMLElement|Node} element
|
||||
* @param {string} propertyName
|
||||
* @param {boolean} prefixVendor
|
||||
* @return {string}
|
||||
*/
|
||||
export const getStyleProperty = (element, propertyName, prefixVendor = false) => {
|
||||
if (prefixVendor) {
|
||||
const prefixes = ['', '-webkit-', '-ms-', 'moz-', '-o-'];
|
||||
for (let counter = 0; counter < prefixes.length; counter++) {
|
||||
const prefixedProperty = prefixes[counter] + propertyName;
|
||||
const foundValue = getStyleProperty(element, prefixedProperty);
|
||||
|
||||
if (foundValue) {
|
||||
return foundValue;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
let propertyValue = '';
|
||||
|
||||
if (element.currentStyle) {
|
||||
propertyValue = element.currentStyle[propertyName];
|
||||
} else if (document.defaultView && document.defaultView.getComputedStyle) {
|
||||
propertyValue = document.defaultView
|
||||
.getComputedStyle(element, null)
|
||||
.getPropertyValue(propertyName);
|
||||
}
|
||||
|
||||
return propertyValue && propertyValue.toLowerCase ? propertyValue.toLowerCase() : propertyValue;
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if the passed element is dom object or not
|
||||
* @param element
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export const isDomElement = function (element) {
|
||||
return element && typeof element === 'object' && 'nodeType' in element;
|
||||
};
|
||||
@@ -1,394 +0,0 @@
|
||||
import {
|
||||
ANIMATION_DURATION_MS,
|
||||
CLASS_DRIVER_HIGHLIGHTED_ELEMENT,
|
||||
CLASS_FIX_STACKING_CONTEXT,
|
||||
CLASS_POSITION_RELATIVE,
|
||||
} from '../common/constants';
|
||||
import { getStyleProperty } from '../common/utils';
|
||||
import Position from './position';
|
||||
|
||||
/**
|
||||
* Wrapper around DOMElements to enrich them
|
||||
* with the functionality necessary
|
||||
*/
|
||||
export default class Element {
|
||||
/**
|
||||
* DOM element object
|
||||
* @param {Node|HTMLElement} node
|
||||
* @param {Object} options
|
||||
* @param {Popover} popover
|
||||
* @param {Stage} stage
|
||||
* @param {Overlay} overlay
|
||||
* @param {Window} window
|
||||
* @param {Document} document
|
||||
*/
|
||||
constructor({
|
||||
node,
|
||||
options,
|
||||
popover,
|
||||
stage,
|
||||
overlay,
|
||||
window,
|
||||
document,
|
||||
} = {}) {
|
||||
this.node = node;
|
||||
this.document = document;
|
||||
this.window = window;
|
||||
this.options = options;
|
||||
this.overlay = overlay;
|
||||
this.popover = popover;
|
||||
this.stage = stage;
|
||||
|
||||
this.animationTimeout = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current element is visible in viewport
|
||||
* @returns {boolean}
|
||||
* @private
|
||||
*/
|
||||
isInView() {
|
||||
let top = this.node.offsetTop;
|
||||
let left = this.node.offsetLeft;
|
||||
const width = this.node.offsetWidth;
|
||||
const height = this.node.offsetHeight;
|
||||
|
||||
let el = this.node;
|
||||
|
||||
while (el.offsetParent) {
|
||||
el = el.offsetParent;
|
||||
top += el.offsetTop;
|
||||
left += el.offsetLeft;
|
||||
}
|
||||
|
||||
return (
|
||||
top >= this.window.pageYOffset &&
|
||||
left >= this.window.pageXOffset &&
|
||||
(top + height) <= (this.window.pageYOffset + this.window.innerHeight) &&
|
||||
(left + width) <= (this.window.pageXOffset + this.window.innerWidth)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually scrolls to the position of element if `scrollIntoView` fails
|
||||
* @private
|
||||
*/
|
||||
scrollManually() {
|
||||
const elementRect = this.node.getBoundingClientRect();
|
||||
const absoluteElementTop = elementRect.top + this.window.pageYOffset;
|
||||
const middle = absoluteElementTop - (this.window.innerHeight / 2);
|
||||
|
||||
this.window.scrollTo(0, middle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Brings the element to middle of the view port if not in view
|
||||
* @private
|
||||
*/
|
||||
bringInView() {
|
||||
if (this.isInView()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If browser does not support scrollIntoView
|
||||
if (!this.node.scrollIntoView) {
|
||||
this.scrollManually();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.node.scrollIntoView(this.options.scrollIntoViewOptions || {
|
||||
behavior: 'instant',
|
||||
block: 'center',
|
||||
});
|
||||
} catch (e) {
|
||||
// `block` option is not allowed in older versions of firefox, scroll manually
|
||||
this.scrollManually();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the calculated position on screen, around which
|
||||
* we need to draw
|
||||
* @public
|
||||
*/
|
||||
getCalculatedPosition() {
|
||||
const body = this.document.body;
|
||||
const documentElement = this.document.documentElement;
|
||||
const window = this.window;
|
||||
|
||||
const scrollTop = this.window.pageYOffset || documentElement.scrollTop || body.scrollTop;
|
||||
const scrollLeft = window.pageXOffset || documentElement.scrollLeft || body.scrollLeft;
|
||||
const elementRect = this.node.getBoundingClientRect();
|
||||
|
||||
return new Position({
|
||||
top: elementRect.top + scrollTop,
|
||||
left: elementRect.left + scrollLeft,
|
||||
right: elementRect.left + scrollLeft + elementRect.width,
|
||||
bottom: elementRect.top + scrollTop + elementRect.height,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the popover for the current element if any
|
||||
* @returns {Popover|*}
|
||||
* @public
|
||||
*/
|
||||
getPopover() {
|
||||
return this.popover;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is called when element is about to be deselected
|
||||
* i.e. when moving the focus to next element of closing
|
||||
* @public
|
||||
*/
|
||||
onDeselected(hideStage = false) {
|
||||
this.hidePopover();
|
||||
|
||||
if (hideStage) {
|
||||
this.hideStage();
|
||||
}
|
||||
|
||||
this.removeHighlightClasses();
|
||||
|
||||
// If there was any animation in progress, cancel that
|
||||
this.window.clearTimeout(this.animationTimeout);
|
||||
|
||||
if (this.options.onDeselected) {
|
||||
this.options.onDeselected(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given element is same as the current element
|
||||
* @param {Element} element
|
||||
* @returns {boolean}
|
||||
* @public
|
||||
*/
|
||||
isSame(element) {
|
||||
if (!element || !element.node) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return element.node === this.node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is called when the element is about to be highlighted
|
||||
* @public
|
||||
*/
|
||||
onHighlightStarted() {
|
||||
if (this.options.onHighlightStarted) {
|
||||
this.options.onHighlightStarted(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Is called when the element has been successfully highlighted
|
||||
* @public
|
||||
*/
|
||||
onHighlighted() {
|
||||
this.showPopover();
|
||||
this.showStage();
|
||||
|
||||
this.addHighlightClasses();
|
||||
|
||||
const highlightedElement = this;
|
||||
const popoverElement = this.popover;
|
||||
|
||||
if (popoverElement && !popoverElement.isInView()) {
|
||||
popoverElement.bringInView();
|
||||
}
|
||||
|
||||
if (!highlightedElement.isInView()) {
|
||||
highlightedElement.bringInView();
|
||||
}
|
||||
|
||||
if (this.options.onHighlighted) {
|
||||
this.options.onHighlighted(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the stacking context fix classes and the highlighter classes
|
||||
* @private
|
||||
*/
|
||||
removeHighlightClasses() {
|
||||
this.node.classList.remove(CLASS_DRIVER_HIGHLIGHTED_ELEMENT);
|
||||
this.node.classList.remove(CLASS_POSITION_RELATIVE);
|
||||
|
||||
const stackFixes = this.document.querySelectorAll(`.${CLASS_FIX_STACKING_CONTEXT}`);
|
||||
for (let counter = 0; counter < stackFixes.length; counter++) {
|
||||
stackFixes[counter].classList.remove(CLASS_FIX_STACKING_CONTEXT);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the highlight class on the current element and "fixes"
|
||||
* the parent nodes if they
|
||||
* @private
|
||||
*/
|
||||
addHighlightClasses() {
|
||||
this.node.classList.add(CLASS_DRIVER_HIGHLIGHTED_ELEMENT);
|
||||
|
||||
// Don't make relative if element already has some position set
|
||||
if (this.canMakeRelative()) {
|
||||
this.node.classList.add(CLASS_POSITION_RELATIVE);
|
||||
}
|
||||
|
||||
// Check and re-define the stacking context
|
||||
this.fixStackingContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* Walks through the parents of the current element and fixes
|
||||
* the stacking context
|
||||
* @private
|
||||
*/
|
||||
fixStackingContext() {
|
||||
let parentNode = this.node.parentNode;
|
||||
while (parentNode) {
|
||||
if (!parentNode.tagName || parentNode.tagName.toLowerCase() === 'body') {
|
||||
break;
|
||||
}
|
||||
|
||||
const zIndex = getStyleProperty(parentNode, 'z-index');
|
||||
const opacity = parseFloat(getStyleProperty(parentNode, 'opacity'));
|
||||
const transform = getStyleProperty(parentNode, 'transform', true);
|
||||
const transformStyle = getStyleProperty(parentNode, 'transform-style', true);
|
||||
const transformBox = getStyleProperty(parentNode, 'transform-box', true);
|
||||
const filter = getStyleProperty(parentNode, 'filter', true);
|
||||
const perspective = getStyleProperty(parentNode, 'perspective', true);
|
||||
|
||||
// Stacking context gets disturbed if
|
||||
// - Parent has z-index
|
||||
// - Opacity is below 0
|
||||
// - Filter/transform or perspective is applied
|
||||
if (
|
||||
/[0-9]+/.test(zIndex) ||
|
||||
opacity < 1 ||
|
||||
(transform && transform !== 'none') ||
|
||||
(transformStyle && transformStyle !== 'flat') ||
|
||||
(transformBox && transformBox !== 'border-box') ||
|
||||
(filter && filter !== 'none') ||
|
||||
(perspective && perspective !== 'none')
|
||||
) {
|
||||
parentNode.classList.add(CLASS_FIX_STACKING_CONTEXT);
|
||||
}
|
||||
|
||||
parentNode = parentNode.parentNode;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if we can make the current element relative or not
|
||||
* @return {boolean}
|
||||
* @private
|
||||
*/
|
||||
canMakeRelative() {
|
||||
const currentPosition = this.getStyleProperty('position');
|
||||
const avoidPositionsList = ['absolute', 'fixed', 'relative'];
|
||||
|
||||
// Because if the element has any of these positions, making it
|
||||
// relative will break the UI
|
||||
return avoidPositionsList.indexOf(currentPosition) === -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current element's CSS attribute value
|
||||
* @param {string} property
|
||||
* @returns string
|
||||
* @private
|
||||
*/
|
||||
getStyleProperty(property) {
|
||||
return getStyleProperty(this.node, property);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the stage behind the element
|
||||
* @public
|
||||
*/
|
||||
showStage() {
|
||||
this.stage.show(this.getCalculatedPosition());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the DOM Element behind this element
|
||||
* @returns {Node|HTMLElement|*}
|
||||
* @public
|
||||
*/
|
||||
getNode() {
|
||||
return this.node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the stage
|
||||
* @public
|
||||
*/
|
||||
hideStage() {
|
||||
this.stage.hide();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the popover if possible
|
||||
* @public
|
||||
*/
|
||||
hidePopover() {
|
||||
if (!this.popover) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.popover.hide();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the popover on the current element
|
||||
* @public
|
||||
*/
|
||||
showPopover() {
|
||||
if (!this.popover) {
|
||||
return;
|
||||
}
|
||||
|
||||
const showAtPosition = this.getCalculatedPosition();
|
||||
|
||||
// For first highlight, show it immediately because there won't be any animation
|
||||
let showAfterMs = ANIMATION_DURATION_MS;
|
||||
// If animation is disabled or if it is the first display, show it immediately
|
||||
if (!this.options.animate || !this.overlay.lastHighlightedElement) {
|
||||
showAfterMs = 0;
|
||||
}
|
||||
|
||||
this.animationTimeout = this.window.setTimeout(() => {
|
||||
this.popover.show(showAtPosition);
|
||||
}, showAfterMs);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {{height: number, width: number}}
|
||||
* @public
|
||||
*/
|
||||
getFullPageSize() {
|
||||
// eslint-disable-next-line prefer-destructuring
|
||||
const body = this.document.body;
|
||||
const html = this.document.documentElement;
|
||||
|
||||
return {
|
||||
height: Math.max(body.scrollHeight, body.offsetHeight, html.scrollHeight, html.offsetHeight),
|
||||
width: Math.max(body.scrollWidth, body.offsetWidth, html.scrollWidth, html.offsetWidth),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the size for popover
|
||||
* @returns {{height: number, width: number}}
|
||||
* @public
|
||||
*/
|
||||
getSize() {
|
||||
return {
|
||||
height: Math.max(this.node.scrollHeight, this.node.offsetHeight),
|
||||
width: Math.max(this.node.scrollWidth, this.node.offsetWidth),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,195 +0,0 @@
|
||||
import { ANIMATION_DURATION_MS, ID_OVERLAY, OVERLAY_HTML } from '../common/constants';
|
||||
import { createNodeFromString } from '../common/utils';
|
||||
|
||||
/**
|
||||
* Responsible for overlay creation and manipulation i.e.
|
||||
* cutting out the visible part, animating between the sections etc
|
||||
*/
|
||||
export default class Overlay {
|
||||
/**
|
||||
* @param {Object} options
|
||||
* @param {Window} window
|
||||
* @param {Document} document
|
||||
*/
|
||||
constructor(options, window, document) {
|
||||
this.options = options;
|
||||
|
||||
this.highlightedElement = null; // currently highlighted dom element (instance of Element)
|
||||
this.lastHighlightedElement = null; // element that was highlighted before current one
|
||||
this.hideTimer = null;
|
||||
|
||||
this.window = window;
|
||||
this.document = document;
|
||||
|
||||
this.removeNode = this.removeNode.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the overlay
|
||||
* @private
|
||||
*/
|
||||
attachNode() {
|
||||
let pageOverlay = this.document.getElementById(ID_OVERLAY);
|
||||
if (!pageOverlay) {
|
||||
pageOverlay = createNodeFromString(OVERLAY_HTML);
|
||||
document.body.appendChild(pageOverlay);
|
||||
}
|
||||
|
||||
this.node = pageOverlay;
|
||||
this.node.style.opacity = '0';
|
||||
|
||||
if (!this.options.animate) {
|
||||
// For non-animation cases remove the overlay because we achieve this overlay by having
|
||||
// a higher box-shadow on the stage. Why are we doing it that way? Because the stage that
|
||||
// is shown "behind" the highlighted element to make it pop out of the screen, it introduces
|
||||
// some stacking contexts issues. To avoid those issues we just make the stage background
|
||||
// transparent and achieve the overlay using the shadow so to make the element below it visible
|
||||
// through the stage even if there are stacking issues.
|
||||
if (this.node.parentElement) {
|
||||
this.node.parentElement.removeChild(this.node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlights the dom element on the screen
|
||||
* @param {Element} element
|
||||
* @public
|
||||
*/
|
||||
highlight(element) {
|
||||
if (!element || !element.node) {
|
||||
console.warn('Invalid element to highlight. Must be an instance of `Element`');
|
||||
return;
|
||||
}
|
||||
|
||||
// If highlighted element is not changed from last time
|
||||
if (element.isSame(this.highlightedElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// There might be hide timer from last time
|
||||
// which might be getting triggered
|
||||
this.window.clearTimeout(this.hideTimer);
|
||||
|
||||
// Trigger the hook for highlight started
|
||||
element.onHighlightStarted();
|
||||
|
||||
// Old element has been deselected
|
||||
if (this.highlightedElement && !this.highlightedElement.isSame(this.lastHighlightedElement)) {
|
||||
this.highlightedElement.onDeselected();
|
||||
}
|
||||
|
||||
// get the position of element around which we need to draw
|
||||
const position = element.getCalculatedPosition();
|
||||
if (!position.canHighlight()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.lastHighlightedElement = this.highlightedElement;
|
||||
this.highlightedElement = element;
|
||||
|
||||
this.show();
|
||||
|
||||
// Element has been highlighted
|
||||
this.highlightedElement.onHighlighted();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the overlay on whole screen
|
||||
* @public
|
||||
*/
|
||||
show() {
|
||||
if (this.node && this.node.parentElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.attachNode();
|
||||
|
||||
window.setTimeout(() => {
|
||||
this.node.style.opacity = `${this.options.opacity}`;
|
||||
this.node.style.position = 'fixed';
|
||||
this.node.style.left = '0';
|
||||
this.node.style.top = '0';
|
||||
this.node.style.bottom = '0';
|
||||
this.node.style.right = '0';
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the currently selected element
|
||||
* @returns {null|*}
|
||||
* @public
|
||||
*/
|
||||
getHighlightedElement() {
|
||||
return this.highlightedElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the element that was highlighted before current element
|
||||
* @returns {null|*}
|
||||
* @public
|
||||
*/
|
||||
getLastHighlightedElement() {
|
||||
return this.lastHighlightedElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the overlay and cancel any listeners
|
||||
* @public
|
||||
*/
|
||||
clear(immediate = false) {
|
||||
// Callback for when overlay is about to be reset
|
||||
if (this.options.onReset) {
|
||||
this.options.onReset(this.highlightedElement);
|
||||
}
|
||||
|
||||
// Deselect the highlighted element if any
|
||||
if (this.highlightedElement) {
|
||||
const hideStage = true;
|
||||
this.highlightedElement.onDeselected(hideStage);
|
||||
}
|
||||
|
||||
this.highlightedElement = null;
|
||||
this.lastHighlightedElement = null;
|
||||
|
||||
if (!this.node) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear any existing timers and remove node
|
||||
this.window.clearTimeout(this.hideTimer);
|
||||
|
||||
if (this.options.animate && !immediate) {
|
||||
this.node.style.opacity = '0';
|
||||
this.hideTimer = this.window.setTimeout(this.removeNode, ANIMATION_DURATION_MS);
|
||||
} else {
|
||||
this.removeNode();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the overlay node if it exists
|
||||
* @private
|
||||
*/
|
||||
removeNode() {
|
||||
if (this.node && this.node.parentElement) {
|
||||
this.node.parentElement.removeChild(this.node);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the overlay i.e. sets the size according to current window size
|
||||
* And moves the highlight around if necessary
|
||||
* @public
|
||||
*/
|
||||
refresh() {
|
||||
// If no highlighted element, cancel the refresh
|
||||
if (!this.highlightedElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Reposition the stage and show popover
|
||||
this.highlightedElement.showPopover();
|
||||
this.highlightedElement.showStage();
|
||||
}
|
||||
}
|
||||
@@ -1,269 +0,0 @@
|
||||
import Element from './element';
|
||||
import {
|
||||
CLASS_BTN_DISABLED,
|
||||
CLASS_CLOSE_BTN,
|
||||
CLASS_NEXT_STEP_BTN,
|
||||
CLASS_POPOVER_DESCRIPTION,
|
||||
CLASS_POPOVER_FOOTER,
|
||||
CLASS_POPOVER_TIP,
|
||||
CLASS_POPOVER_TITLE,
|
||||
CLASS_PREV_STEP_BTN,
|
||||
ID_POPOVER,
|
||||
POPOVER_HTML,
|
||||
} from '../common/constants';
|
||||
import { createNodeFromString } from '../common/utils';
|
||||
|
||||
/**
|
||||
* Popover that is displayed on top of the highlighted element
|
||||
*/
|
||||
export default class Popover extends Element {
|
||||
/**
|
||||
* @param {Object} options
|
||||
* @param {Window} window
|
||||
* @param {Document} document
|
||||
*/
|
||||
constructor(options, window, document) {
|
||||
super();
|
||||
|
||||
this.options = {
|
||||
isFirst: true,
|
||||
isLast: true,
|
||||
totalCount: 1,
|
||||
currentIndex: 0,
|
||||
showButtons: true,
|
||||
closeBtnText: 'Close',
|
||||
doneBtnText: 'Done',
|
||||
startBtnText: 'Next →',
|
||||
nextBtnText: 'Next →',
|
||||
prevBtnText: '← Previous',
|
||||
...options,
|
||||
};
|
||||
|
||||
this.window = window;
|
||||
this.document = document;
|
||||
|
||||
this.attachNode();
|
||||
this.hide();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the dom element for popover
|
||||
* @private
|
||||
*/
|
||||
attachNode() {
|
||||
let popover = this.document.getElementById(ID_POPOVER);
|
||||
if (!popover) {
|
||||
popover = createNodeFromString(POPOVER_HTML);
|
||||
document.body.appendChild(popover);
|
||||
}
|
||||
|
||||
this.node = popover;
|
||||
this.tipNode = popover.querySelector(`.${CLASS_POPOVER_TIP}`);
|
||||
this.titleNode = popover.querySelector(`.${CLASS_POPOVER_TITLE}`);
|
||||
this.descriptionNode = popover.querySelector(`.${CLASS_POPOVER_DESCRIPTION}`);
|
||||
this.footerNode = popover.querySelector(`.${CLASS_POPOVER_FOOTER}`);
|
||||
this.nextBtnNode = popover.querySelector(`.${CLASS_NEXT_STEP_BTN}`);
|
||||
this.prevBtnNode = popover.querySelector(`.${CLASS_PREV_STEP_BTN}`);
|
||||
this.closeBtnNode = popover.querySelector(`.${CLASS_CLOSE_BTN}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the title node for the popover
|
||||
* @returns {Element | null | *}
|
||||
* @public
|
||||
*/
|
||||
getTitleNode() {
|
||||
return this.titleNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the description node for the popover
|
||||
* @returns {Element | null | *}
|
||||
* @public
|
||||
*/
|
||||
getDescriptionNode() {
|
||||
return this.descriptionNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the popover
|
||||
* @public
|
||||
*/
|
||||
hide() {
|
||||
this.node.style.display = 'none';
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default state for the popover
|
||||
* @private
|
||||
*/
|
||||
setInitialState() {
|
||||
this.node.style.display = 'block';
|
||||
this.node.style.left = '0';
|
||||
this.node.style.top = '0';
|
||||
this.node.style.bottom = '';
|
||||
this.node.style.right = '';
|
||||
|
||||
// Remove the positional classes from tip
|
||||
this.node
|
||||
.querySelector(`.${CLASS_POPOVER_TIP}`)
|
||||
.className = CLASS_POPOVER_TIP;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the popover at the given position
|
||||
* @param {Position} position
|
||||
* @public
|
||||
*/
|
||||
show(position) {
|
||||
this.setInitialState();
|
||||
|
||||
// Set the title and descriptions
|
||||
this.titleNode.innerHTML = this.options.title;
|
||||
this.descriptionNode.innerHTML = this.options.description || '';
|
||||
|
||||
this.renderFooter();
|
||||
|
||||
// Position the popover around the given position
|
||||
switch (this.options.position) {
|
||||
case 'left':
|
||||
this.positionOnLeft(position);
|
||||
break;
|
||||
case 'right':
|
||||
this.positionOnRight(position);
|
||||
break;
|
||||
case 'top':
|
||||
this.positionOnTop(position);
|
||||
break;
|
||||
case 'bottom':
|
||||
this.positionOnBottom(position);
|
||||
break;
|
||||
case 'auto':
|
||||
default:
|
||||
this.autoPosition(position);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables, disables buttons, sets the text and
|
||||
* decides if to show them or not
|
||||
* @private
|
||||
*/
|
||||
renderFooter() {
|
||||
this.nextBtnNode.innerHTML = this.options.nextBtnText;
|
||||
this.prevBtnNode.innerHTML = this.options.prevBtnText;
|
||||
this.closeBtnNode.innerHTML = this.options.closeBtnText;
|
||||
|
||||
// If there was only one item, hide the buttons
|
||||
if (!this.options.showButtons || !this.options.totalCount || this.options.totalCount === 1) {
|
||||
this.footerNode.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
this.footerNode.style.display = 'block';
|
||||
if (this.options.isFirst) {
|
||||
this.prevBtnNode.classList.add(CLASS_BTN_DISABLED);
|
||||
this.nextBtnNode.innerHTML = this.options.startBtnText;
|
||||
} else {
|
||||
this.prevBtnNode.classList.remove(CLASS_BTN_DISABLED);
|
||||
}
|
||||
|
||||
if (this.options.isLast) {
|
||||
this.nextBtnNode.innerHTML = this.options.doneBtnText;
|
||||
} else {
|
||||
this.nextBtnNode.innerHTML = this.options.nextBtnText;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the popover on the left of the given position
|
||||
* @param {Position} elementPosition
|
||||
* @private
|
||||
*/
|
||||
positionOnLeft(elementPosition) {
|
||||
const popoverWidth = this.getSize().width;
|
||||
const popoverMargin = this.options.padding + 10; // adding 10 to give it a little distance from the element
|
||||
|
||||
this.node.style.left = `${elementPosition.left - popoverWidth - popoverMargin}px`;
|
||||
this.node.style.top = `${elementPosition.top - this.options.padding}px`;
|
||||
this.node.style.right = '';
|
||||
this.node.style.bottom = '';
|
||||
|
||||
this.tipNode.classList.add('right');
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the popover on the right of the given position
|
||||
* @param {Position} elementPosition
|
||||
* @private
|
||||
*/
|
||||
positionOnRight(elementPosition) {
|
||||
const popoverMargin = this.options.padding + 10; // adding 10 to give it a little distance from the element
|
||||
|
||||
this.node.style.left = `${elementPosition.right + popoverMargin}px`;
|
||||
this.node.style.top = `${elementPosition.top - this.options.padding}px`;
|
||||
this.node.style.right = '';
|
||||
this.node.style.bottom = '';
|
||||
|
||||
this.tipNode.classList.add('left');
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the popover on the top of the given position
|
||||
* @param {Position} elementPosition
|
||||
* @private
|
||||
*/
|
||||
positionOnTop(elementPosition) {
|
||||
const popoverHeight = this.getSize().height;
|
||||
const popoverMargin = this.options.padding + 10; // adding 10 to give it a little distance from the element
|
||||
|
||||
this.node.style.top = `${elementPosition.top - popoverHeight - popoverMargin}px`;
|
||||
this.node.style.left = `${elementPosition.left - this.options.padding}px`;
|
||||
this.node.style.right = '';
|
||||
this.node.style.bottom = '';
|
||||
|
||||
this.tipNode.classList.add('bottom');
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the popover on the bottom of the given position
|
||||
* @param {Position} elementPosition
|
||||
* @private
|
||||
*/
|
||||
positionOnBottom(elementPosition) {
|
||||
const popoverMargin = this.options.padding + 10; // adding 10 to give it a little distance from the element
|
||||
|
||||
this.node.style.top = `${elementPosition.bottom + popoverMargin}px`;
|
||||
this.node.style.left = `${elementPosition.left - this.options.padding}px`;
|
||||
this.node.style.right = '';
|
||||
this.node.style.bottom = '';
|
||||
|
||||
this.tipNode.classList.add('top');
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically positions the popover around the given position
|
||||
* such that the element and popover remain in view
|
||||
* @todo add the left and right positioning decisions
|
||||
* @param {Position} elementPosition
|
||||
* @private
|
||||
*/
|
||||
autoPosition(elementPosition) {
|
||||
const pageSize = this.getFullPageSize();
|
||||
const popoverSize = this.getSize();
|
||||
|
||||
const pageHeight = pageSize.height;
|
||||
const popoverHeight = popoverSize.height;
|
||||
const popoverMargin = this.options.padding + 10; // adding 10 to give it a little distance from the element
|
||||
|
||||
const pageHeightAfterPopOver = elementPosition.bottom + popoverHeight + popoverMargin;
|
||||
|
||||
// If adding popover would go out of the window height, then show it to the top
|
||||
if (pageHeightAfterPopOver >= pageHeight) {
|
||||
this.positionOnTop(elementPosition);
|
||||
} else {
|
||||
this.positionOnBottom(elementPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/**
|
||||
* Responsible for validating positions and is used
|
||||
* when manipulating positions across the application
|
||||
*/
|
||||
export default class Position {
|
||||
/**
|
||||
* @param {number} left
|
||||
* @param {number} top
|
||||
* @param {number} right
|
||||
* @param {number} bottom
|
||||
*/
|
||||
constructor({
|
||||
left = 0,
|
||||
top = 0,
|
||||
right = 0,
|
||||
bottom = 0,
|
||||
} = {}) {
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
this.top = top;
|
||||
this.bottom = bottom;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the position is valid to be highlighted
|
||||
* @returns {boolean}
|
||||
* @public
|
||||
*/
|
||||
canHighlight() {
|
||||
return this.left < this.right && this.top < this.bottom;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given position is equal to the passed position
|
||||
* @param position Position
|
||||
* @returns {boolean}
|
||||
* @public
|
||||
*/
|
||||
equals(position) {
|
||||
return Math.round(this.left) === Math.round(position.left) &&
|
||||
Math.round(this.right) === Math.round(position.right) &&
|
||||
Math.round(this.top) === Math.round(position.top) &&
|
||||
Math.round(this.bottom) === Math.round(position.bottom);
|
||||
}
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
import { CLASS_STAGE_NO_ANIMATION, ID_STAGE, STAGE_HTML } from '../common/constants';
|
||||
import { createNodeFromString } from '../common/utils';
|
||||
import Element from './element';
|
||||
|
||||
/**
|
||||
* Stage behind the highlighted element to give it a little
|
||||
* highlight from rest of the page
|
||||
*/
|
||||
export default class Stage extends Element {
|
||||
/**
|
||||
* @param {Object} options
|
||||
* @param {Window} window
|
||||
* @param {Document} document
|
||||
*/
|
||||
constructor(options, window, document) {
|
||||
super();
|
||||
|
||||
this.options = options;
|
||||
this.window = window;
|
||||
this.document = document;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the DOM element if not already there
|
||||
* @private
|
||||
*/
|
||||
attachNode() {
|
||||
let stage = this.document.getElementById(ID_STAGE);
|
||||
if (!stage) {
|
||||
stage = createNodeFromString(STAGE_HTML);
|
||||
document.body.appendChild(stage);
|
||||
}
|
||||
|
||||
this.node = stage;
|
||||
|
||||
if (!this.options.animate) {
|
||||
this.node.classList.add(CLASS_STAGE_NO_ANIMATION);
|
||||
} else {
|
||||
this.node.classList.remove(CLASS_STAGE_NO_ANIMATION);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simply hides the stage
|
||||
* @public
|
||||
*/
|
||||
hide() {
|
||||
if (!this.node || !this.node.parentElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.node.parentElement.removeChild(this.node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes it visible and sets the default properties
|
||||
* @private
|
||||
*/
|
||||
setInitialStyle() {
|
||||
this.node.style.display = 'block';
|
||||
this.node.style.left = '0';
|
||||
this.node.style.top = '0';
|
||||
this.node.style.bottom = '';
|
||||
this.node.style.right = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the stage at provided position
|
||||
* @param {Position} position
|
||||
* @public
|
||||
*/
|
||||
show(position) {
|
||||
this.attachNode();
|
||||
|
||||
this.setInitialStyle();
|
||||
|
||||
// Make it two times the padding because, half will be given on left and half on right
|
||||
const requiredPadding = this.options.padding * 2;
|
||||
|
||||
const width = (position.right - position.left) + (requiredPadding);
|
||||
const height = (position.bottom - position.top) + (requiredPadding);
|
||||
|
||||
// Show the stage
|
||||
this.node.style.display = 'block';
|
||||
this.node.style.position = 'absolute';
|
||||
this.node.style.width = `${width}px`;
|
||||
this.node.style.height = `${height}px`;
|
||||
this.node.style.top = `${position.top - (requiredPadding / 2)}px`;
|
||||
this.node.style.left = `${position.left - (requiredPadding / 2)}px`;
|
||||
this.node.style.backgroundColor = this.options.stageBackground;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,214 +0,0 @@
|
||||
$text-color: #2d2d2d !default;
|
||||
$popover-bg: #ffffff !default;
|
||||
$stage-bg: #ffffff !default;
|
||||
$button-bg: #f1f1f1 !default;
|
||||
$disabled-btn-color: #808080 !default;
|
||||
|
||||
$popover-z-index: 1000000000 !default;
|
||||
$overlay-z-index: 100002 !default;
|
||||
$stage-z-index: 100003 !default;
|
||||
$highlighted-element-z-index: 100004 !default;
|
||||
|
||||
// If you update this duration, make sure to
|
||||
// update `ANIMATION_DURATION_MS` constant
|
||||
$animation-ms: 400 !default;
|
||||
$animation-sec: ($animation-ms / 1000) * 1s !default;
|
||||
|
||||
div#driver-popover-item {
|
||||
display: none;
|
||||
position: absolute;
|
||||
background: $popover-bg;
|
||||
color: $text-color;
|
||||
margin: 0;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
min-width: 250px;
|
||||
max-width: 300px;
|
||||
box-shadow: 0 1px 10px rgba(0, 0, 0, 0.4);
|
||||
z-index: $popover-z-index;
|
||||
|
||||
.driver-popover-tip {
|
||||
border: 5px solid $popover-bg;
|
||||
content: '';
|
||||
position: absolute;
|
||||
|
||||
&.bottom {
|
||||
bottom: -10px;
|
||||
border-top-color: $popover-bg;
|
||||
border-right-color: transparent;
|
||||
border-bottom-color: transparent;
|
||||
border-left-color: transparent;
|
||||
}
|
||||
|
||||
&.left {
|
||||
left: -10px;
|
||||
top: 10px;
|
||||
border-top-color: transparent;
|
||||
border-right-color: $popover-bg;
|
||||
border-bottom-color: transparent;
|
||||
border-left-color: transparent;
|
||||
}
|
||||
|
||||
&.right {
|
||||
right: -10px;
|
||||
top: 10px;
|
||||
border-top-color: transparent;
|
||||
border-right-color: transparent;
|
||||
border-bottom-color: transparent;
|
||||
border-left-color: $popover-bg;
|
||||
}
|
||||
|
||||
&.top {
|
||||
top: -10px;
|
||||
border-top-color: transparent;
|
||||
border-right-color: transparent;
|
||||
border-bottom-color: $popover-bg;
|
||||
border-left-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.driver-popover-footer {
|
||||
display: block;
|
||||
clear: both;
|
||||
margin-top: 5px;
|
||||
|
||||
button {
|
||||
display: inline-block;
|
||||
padding: 3px 10px;
|
||||
border: 1px solid #d4d4d4;
|
||||
text-decoration: none;
|
||||
text-shadow: 1px 1px 0 #fff;
|
||||
color: $text-color;
|
||||
font: 11px/normal sans-serif;
|
||||
cursor: pointer;
|
||||
outline: 0;
|
||||
background-color: $button-bg;
|
||||
border-radius: 2px;
|
||||
zoom: 1;
|
||||
margin: 10px 0 0;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
button.driver-disabled {
|
||||
color: $disabled-btn-color;
|
||||
cursor: default;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.driver-close-btn {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.driver-btn-group {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
|
||||
.driver-popover-title {
|
||||
font: 19px/normal sans-serif;
|
||||
margin: 0 0 5px;
|
||||
font-weight: bold;
|
||||
display: block;
|
||||
position: relative;
|
||||
line-height: 1.5;
|
||||
zoom: 1;
|
||||
}
|
||||
|
||||
.driver-popover-description {
|
||||
margin-bottom: 0;
|
||||
font: 14px/normal sans-serif;
|
||||
line-height: 1.5;
|
||||
color: $text-color;
|
||||
font-weight: normal;
|
||||
zoom: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.driver-stage-no-animation {
|
||||
-webkit-transition: none !important;
|
||||
-moz-transition: none !important;
|
||||
-ms-transition: none !important;
|
||||
-o-transition: none !important;
|
||||
transition: none !important;
|
||||
|
||||
background: transparent !important;
|
||||
outline: 5000px solid rgba(0, 0, 0, 0.75);
|
||||
}
|
||||
|
||||
div#driver-page-overlay {
|
||||
background: black;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
zoom: 1;
|
||||
filter: alpha(opacity=75);
|
||||
opacity: 0.75;
|
||||
z-index: $overlay-z-index !important;
|
||||
|
||||
-webkit-transition: all $animation-sec;
|
||||
-moz-transition: all $animation-sec;
|
||||
-ms-transition: all $animation-sec;
|
||||
-o-transition: all $animation-sec;
|
||||
transition: all $animation-sec;
|
||||
}
|
||||
|
||||
div#driver-highlighted-element-stage {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 50px;
|
||||
width: 300px;
|
||||
background: $stage-bg;
|
||||
z-index: $stage-z-index !important;
|
||||
display: none;
|
||||
border-radius: 2px;
|
||||
|
||||
-webkit-transition: all $animation-sec;
|
||||
-moz-transition: all $animation-sec;
|
||||
-ms-transition: all $animation-sec;
|
||||
-o-transition: all $animation-sec;
|
||||
transition: all $animation-sec;
|
||||
}
|
||||
|
||||
.driver-highlighted-element {
|
||||
z-index: $highlighted-element-z-index !important;
|
||||
}
|
||||
|
||||
.driver-position-relative {
|
||||
position: relative !important;
|
||||
}
|
||||
|
||||
.driver-fix-stacking {
|
||||
z-index: auto !important;
|
||||
opacity: 1.0 !important;
|
||||
-webkit-transform: none !important;
|
||||
-moz-transform: none !important;
|
||||
-ms-transform: none !important;
|
||||
-o-transform: none !important;
|
||||
transform: none !important;
|
||||
-webkit-filter: none !important;
|
||||
-moz-filter: none !important;
|
||||
-ms-filter: none !important;
|
||||
-o-filter: none !important;
|
||||
filter: none !important;
|
||||
-webkit-perspective: none !important;
|
||||
-moz-perspective: none !important;
|
||||
-ms-perspective: none !important;
|
||||
-o-perspective: none !important;
|
||||
perspective: none !important;
|
||||
-webkit-transform-style: flat !important;
|
||||
-moz-transform-style: flat !important;
|
||||
-ms-transform-style: flat !important;
|
||||
transform-style: flat !important;
|
||||
-webkit-transform-box: border-box !important;
|
||||
-moz-transform-box: border-box !important;
|
||||
-ms-transform-box: border-box !important;
|
||||
-o-transform-box: border-box !important;
|
||||
transform-box: border-box !important;
|
||||
will-change: unset !important;
|
||||
}
|
||||
@@ -1,416 +0,0 @@
|
||||
import Overlay from './core/overlay';
|
||||
import Element from './core/element';
|
||||
import Popover from './core/popover';
|
||||
import {
|
||||
CLASS_CLOSE_BTN,
|
||||
CLASS_NEXT_STEP_BTN,
|
||||
CLASS_PREV_STEP_BTN,
|
||||
ESC_KEY_CODE,
|
||||
ID_POPOVER,
|
||||
LEFT_KEY_CODE,
|
||||
OVERLAY_OPACITY,
|
||||
OVERLAY_PADDING,
|
||||
RIGHT_KEY_CODE,
|
||||
SHOULD_ANIMATE_OVERLAY,
|
||||
SHOULD_OUTSIDE_CLICK_CLOSE,
|
||||
SHOULD_OUTSIDE_CLICK_NEXT,
|
||||
ALLOW_KEYBOARD_CONTROL,
|
||||
} from './common/constants';
|
||||
import Stage from './core/stage';
|
||||
import { isDomElement } from './common/utils';
|
||||
|
||||
/**
|
||||
* Plugin class that drives the plugin
|
||||
*/
|
||||
export default class Driver {
|
||||
/**
|
||||
* @param {Object} options
|
||||
*/
|
||||
constructor(options = {}) {
|
||||
this.options = {
|
||||
animate: SHOULD_ANIMATE_OVERLAY, // Whether to animate or not
|
||||
opacity: OVERLAY_OPACITY, // Overlay opacity
|
||||
padding: OVERLAY_PADDING, // Spacing around the element from the overlay
|
||||
scrollIntoViewOptions: null, // Options to be passed to `scrollIntoView`
|
||||
allowClose: SHOULD_OUTSIDE_CLICK_CLOSE, // Whether to close overlay on click outside the element
|
||||
keyboardControl: ALLOW_KEYBOARD_CONTROL, // Whether to allow controlling through keyboard or not
|
||||
overlayClickNext: SHOULD_OUTSIDE_CLICK_NEXT, // Whether to move next on click outside the element
|
||||
stageBackground: '#ffffff', // Background color for the stage
|
||||
onHighlightStarted: () => null, // When element is about to be highlighted
|
||||
onHighlighted: () => null, // When element has been highlighted
|
||||
onDeselected: () => null, // When the element has been deselected
|
||||
onReset: () => null, // When overlay is about to be cleared
|
||||
onNext: () => null, // When next button is clicked
|
||||
onPrevious: () => null, // When previous button is clicked
|
||||
...options,
|
||||
};
|
||||
|
||||
this.document = document;
|
||||
this.window = window;
|
||||
this.isActivated = false;
|
||||
this.steps = []; // steps to be presented if any
|
||||
this.currentStep = 0; // index for the currently highlighted step
|
||||
this.currentMovePrevented = false; // If the current move was prevented
|
||||
|
||||
this.overlay = new Overlay(this.options, window, document);
|
||||
|
||||
this.onResize = this.onResize.bind(this);
|
||||
this.onKeyUp = this.onKeyUp.bind(this);
|
||||
this.onClick = this.onClick.bind(this);
|
||||
this.moveNext = this.moveNext.bind(this);
|
||||
this.movePrevious = this.movePrevious.bind(this);
|
||||
this.preventMove = this.preventMove.bind(this);
|
||||
|
||||
// Event bindings
|
||||
this.bind();
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds any DOM events listeners
|
||||
* @todo: add throttling in all the listeners
|
||||
* @private
|
||||
*/
|
||||
bind() {
|
||||
this.window.addEventListener('resize', this.onResize, false);
|
||||
this.window.addEventListener('keyup', this.onKeyUp, false);
|
||||
this.window.addEventListener('click', this.onClick, false);
|
||||
this.window.addEventListener('touchstart', this.onClick, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the popover if clicked outside the highlighted element
|
||||
* or outside the
|
||||
* @param e
|
||||
* @private
|
||||
*/
|
||||
onClick(e) {
|
||||
if (!this.isActivated || !this.hasHighlightedElement()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const highlightedElement = this.overlay.getHighlightedElement();
|
||||
const popover = this.document.getElementById(ID_POPOVER);
|
||||
|
||||
const clickedHighlightedElement = highlightedElement.node.contains(e.target);
|
||||
const clickedPopover = popover && popover.contains(e.target);
|
||||
|
||||
if (!clickedHighlightedElement && !clickedPopover && this.options.overlayClickNext) {
|
||||
this.handleNext();
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove the overlay If clicked outside the highlighted element
|
||||
if (!clickedHighlightedElement && !clickedPopover && this.options.allowClose) {
|
||||
this.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
const nextClicked = e.target.classList.contains(CLASS_NEXT_STEP_BTN);
|
||||
const prevClicked = e.target.classList.contains(CLASS_PREV_STEP_BTN);
|
||||
const closeClicked = e.target.classList.contains(CLASS_CLOSE_BTN);
|
||||
|
||||
if (closeClicked) {
|
||||
this.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
if (nextClicked) {
|
||||
this.handleNext();
|
||||
} else if (prevClicked) {
|
||||
this.handlePrevious();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handler for the onResize DOM event
|
||||
* Makes sure highlighted element stays at valid position
|
||||
* @private
|
||||
*/
|
||||
onResize() {
|
||||
if (!this.isActivated) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.overlay.refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the overlay on escape key process
|
||||
* @param event
|
||||
* @private
|
||||
*/
|
||||
onKeyUp(event) {
|
||||
// If driver is not active or keyboard control is disabled
|
||||
if (!this.isActivated || !this.options.keyboardControl) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If escape was pressed and it is allowed to click outside to close
|
||||
if (event.keyCode === ESC_KEY_CODE) {
|
||||
this.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
// Arrow keys to only perform if it is stepped introduction
|
||||
if (this.steps.length !== 0) {
|
||||
if (event.keyCode === RIGHT_KEY_CODE) {
|
||||
this.handleNext();
|
||||
} else if (event.keyCode === LEFT_KEY_CODE) {
|
||||
this.handlePrevious();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves to the previous step if possible
|
||||
* otherwise resets the overlay
|
||||
* @public
|
||||
*/
|
||||
movePrevious() {
|
||||
const previousStep = this.steps[this.currentStep - 1];
|
||||
if (!previousStep) {
|
||||
this.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
this.overlay.highlight(previousStep);
|
||||
this.currentStep -= 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevents the current move. Useful in `onNext` if you want to
|
||||
* perform some asynchronous task and manually move to next step
|
||||
* @public
|
||||
*/
|
||||
preventMove() {
|
||||
this.currentMovePrevented = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the internal "move to next" event
|
||||
* @private
|
||||
*/
|
||||
handleNext() {
|
||||
this.currentMovePrevented = false;
|
||||
|
||||
// Call the bound `onNext` handler if available
|
||||
const currentStep = this.steps[this.currentStep];
|
||||
if (currentStep.options.onNext) {
|
||||
currentStep.options.onNext(this.overlay.highlightedElement);
|
||||
}
|
||||
|
||||
if (this.currentMovePrevented) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.moveNext();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the internal "move to previous" event
|
||||
* @private
|
||||
*/
|
||||
handlePrevious() {
|
||||
this.currentMovePrevented = false;
|
||||
|
||||
// Call the bound `onPrevious` handler if available
|
||||
const currentStep = this.steps[this.currentStep];
|
||||
if (currentStep.options.onPrevious) {
|
||||
currentStep.options.onPrevious(this.overlay.highlightedElement);
|
||||
}
|
||||
|
||||
if (this.currentMovePrevented) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.movePrevious();
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves to the next step if possible
|
||||
* otherwise resets the overlay
|
||||
* @public
|
||||
*/
|
||||
moveNext() {
|
||||
const nextStep = this.steps[this.currentStep + 1];
|
||||
if (!nextStep) {
|
||||
this.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
this.overlay.highlight(nextStep);
|
||||
this.currentStep += 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
* @public
|
||||
*/
|
||||
hasNextStep() {
|
||||
return !!this.steps[this.currentStep + 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
* @public
|
||||
*/
|
||||
hasPreviousStep() {
|
||||
return !!this.steps[this.currentStep - 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the steps if any and clears the overlay
|
||||
* @param {boolean} immediate
|
||||
* @public
|
||||
*/
|
||||
reset(immediate = false) {
|
||||
this.currentStep = 0;
|
||||
this.isActivated = false;
|
||||
this.overlay.clear(immediate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if there is any highlighted element or not
|
||||
* @returns {boolean}
|
||||
* @public
|
||||
*/
|
||||
hasHighlightedElement() {
|
||||
const highlightedElement = this.overlay.getHighlightedElement();
|
||||
return highlightedElement && highlightedElement.node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the currently highlighted element in overlay
|
||||
* @returns {Element}
|
||||
* @public
|
||||
*/
|
||||
getHighlightedElement() {
|
||||
return this.overlay.getHighlightedElement();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the element that was highlighted before currently highlighted element
|
||||
* @returns {Element}
|
||||
* @public
|
||||
*/
|
||||
getLastHighlightedElement() {
|
||||
return this.overlay.getLastHighlightedElement();
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines steps to be highlighted
|
||||
* @param {array} steps
|
||||
* @public
|
||||
*/
|
||||
defineSteps(steps) {
|
||||
this.steps = [];
|
||||
|
||||
for (let counter = 0; counter < steps.length; counter++) {
|
||||
const element = this.prepareElementFromStep(steps[counter], steps, counter);
|
||||
if (!element) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.steps.push(element);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the step received from the user and returns an instance
|
||||
* of Element
|
||||
*
|
||||
* @param currentStep Step that is being prepared
|
||||
* @param allSteps List of all the steps
|
||||
* @param index Index of the current step
|
||||
* @returns {null|Element}
|
||||
* @private
|
||||
*/
|
||||
prepareElementFromStep(currentStep, allSteps = [], index = 0) {
|
||||
let elementOptions = {};
|
||||
let querySelector = currentStep;
|
||||
|
||||
// If the `currentStep` is step definition
|
||||
// then grab the options and element from the definition
|
||||
const isStepDefinition = typeof currentStep !== 'string' && !isDomElement(currentStep);
|
||||
|
||||
if (!currentStep || (isStepDefinition && !currentStep.element)) {
|
||||
throw new Error(`Element is required in step ${index}`);
|
||||
}
|
||||
|
||||
if (isStepDefinition) {
|
||||
querySelector = currentStep.element;
|
||||
elementOptions = { ...this.options, ...currentStep };
|
||||
}
|
||||
|
||||
// If the given element is a query selector or a DOM element?
|
||||
const domElement = isDomElement(querySelector) ? querySelector : this.document.querySelector(querySelector);
|
||||
if (!domElement) {
|
||||
console.warn(`Element to highlight ${querySelector} not found`);
|
||||
return null;
|
||||
}
|
||||
|
||||
let popover = null;
|
||||
if (elementOptions.popover && elementOptions.popover.title) {
|
||||
const popoverOptions = {
|
||||
...this.options,
|
||||
...elementOptions.popover,
|
||||
totalCount: allSteps.length,
|
||||
currentIndex: index,
|
||||
isFirst: index === 0,
|
||||
isLast: index === allSteps.length - 1,
|
||||
};
|
||||
|
||||
popover = new Popover(popoverOptions, this.window, this.document);
|
||||
}
|
||||
|
||||
const stageOptions = {
|
||||
...this.options,
|
||||
...elementOptions,
|
||||
};
|
||||
|
||||
const stage = new Stage(stageOptions, this.window, this.document);
|
||||
|
||||
return new Element({
|
||||
node: domElement,
|
||||
options: elementOptions,
|
||||
popover,
|
||||
stage,
|
||||
overlay: this.overlay,
|
||||
window: this.window,
|
||||
document: this.document,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates highlighting steps from first step
|
||||
* @param {number} index at which highlight is to be started
|
||||
* @public
|
||||
*/
|
||||
start(index = 0) {
|
||||
if (!this.steps || this.steps.length === 0) {
|
||||
throw new Error('There are no steps defined to iterate');
|
||||
}
|
||||
|
||||
this.isActivated = true;
|
||||
|
||||
this.currentStep = index;
|
||||
this.overlay.highlight(this.steps[index]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlights the given element
|
||||
* @param {string|{element: string, popover: {}}} selector Query selector or a step definition
|
||||
* @public
|
||||
*/
|
||||
highlight(selector) {
|
||||
this.isActivated = true;
|
||||
|
||||
const element = this.prepareElementFromStep(selector);
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.overlay.highlight(element);
|
||||
}
|
||||
}
|
||||
@@ -1,784 +0,0 @@
|
||||
declare module 'driver.js' {
|
||||
class Driver {
|
||||
/**
|
||||
* Refers to the global document object
|
||||
*/
|
||||
private document: Document;
|
||||
|
||||
/**
|
||||
* Refers to the global window object
|
||||
*/
|
||||
private window: Window;
|
||||
|
||||
/**
|
||||
* If the driver is active or not
|
||||
*/
|
||||
public isActivated: boolean;
|
||||
|
||||
/**
|
||||
* Flag for if the current move was prevented. It is used in
|
||||
* onNext() or onPrevious() callbacks to stop the current transition
|
||||
*/
|
||||
private currentMovePrevented: boolean;
|
||||
|
||||
/**
|
||||
* Refers to the array of steps to be presented if any
|
||||
*/
|
||||
private steps: Array<Driver.Step>;
|
||||
|
||||
/**
|
||||
* Refers to step index that is currently active
|
||||
*/
|
||||
private currentStep: number;
|
||||
|
||||
/**
|
||||
* Flag for if the current move was prevented or not
|
||||
*/
|
||||
private currentMovePrevented: boolean;
|
||||
|
||||
/**
|
||||
* Refers to the overlay for the screen
|
||||
*/
|
||||
private overlay: Driver.Overlay;
|
||||
|
||||
/**
|
||||
* @param {DriverOptions} options
|
||||
*/
|
||||
public constructor(options?: Driver.DriverOptions);
|
||||
|
||||
/**
|
||||
* Does the required bindings for DOM Events
|
||||
*/
|
||||
private bind(): void;
|
||||
|
||||
/**
|
||||
* Listener for the click event, to decide if
|
||||
* to next/previous step, reset the overlay etc
|
||||
* @param {Event} e
|
||||
*/
|
||||
private onClick(e: Event): void;
|
||||
|
||||
/**
|
||||
* Refreshes the driver and resets the position for stage
|
||||
* and popover on resizing the window
|
||||
*/
|
||||
private onResize(): void;
|
||||
|
||||
/**
|
||||
* Makes it operable with keyboard
|
||||
* @param {Event} e
|
||||
*/
|
||||
private onKeyUp(e: Event): void;
|
||||
|
||||
/**
|
||||
* Handles the internal next event
|
||||
*/
|
||||
private handleNext(): void;
|
||||
|
||||
/**
|
||||
* Handles the internal previous event
|
||||
*/
|
||||
private handlePrevious(): void;
|
||||
|
||||
/**
|
||||
* Prevents the current move. Useful in `onNext` if you want to
|
||||
* perform some asynchronous task and manually move to next step
|
||||
*/
|
||||
public preventMove(): void;
|
||||
|
||||
/**
|
||||
* Moves to the previous step if possible
|
||||
* otherwise resets the overlay
|
||||
*/
|
||||
public movePrevious(): void;
|
||||
|
||||
/**
|
||||
* Moves to the next step if possible
|
||||
* otherwise resets the overlay
|
||||
*/
|
||||
public moveNext(): void;
|
||||
|
||||
/**
|
||||
* Prevents the current move. Useful in `onNext` if you want to
|
||||
* perform some asynchronous task and manually move to next step
|
||||
*/
|
||||
preventMove(): void;
|
||||
|
||||
/**
|
||||
* Checks if can be moved to next step
|
||||
* @return {boolean}
|
||||
*/
|
||||
public hasNextStep(): boolean;
|
||||
|
||||
/**
|
||||
* Checks if can be moved to previous step
|
||||
* @return {boolean}
|
||||
*/
|
||||
public hasPreviousStep(): boolean;
|
||||
|
||||
/**
|
||||
* Resets the steps and clears the overlay
|
||||
*/
|
||||
public reset(immediate?: boolean): void;
|
||||
|
||||
/**
|
||||
* Checks if there is any highlighted element or not
|
||||
* @return {boolean}
|
||||
*/
|
||||
public hasHighlightedElement(): boolean;
|
||||
|
||||
/**
|
||||
* Gets the currently highlighted element if any
|
||||
* @return {Driver.Element}
|
||||
*/
|
||||
public getHighlightedElement(): Driver.Element | null;
|
||||
|
||||
/**
|
||||
* Gets the last highlighted element if any
|
||||
* @return {Driver.Element}
|
||||
*/
|
||||
public getLastHighlightedElement(): Driver.Element | null;
|
||||
|
||||
/**
|
||||
* Defines the steps to be used in multi-step driver
|
||||
* @param {Array<Driver.Step>} steps
|
||||
*/
|
||||
public defineSteps(steps: Array<Driver.Step>): void;
|
||||
|
||||
/**
|
||||
* Prepares {Driver.Element} from the given step definition
|
||||
* @param {Driver.Step | string} step query selector or step definition for the step
|
||||
* @param {Array<Driver.Step>} allSteps all the given steps
|
||||
* @param {number} stepIndex array index for the current step
|
||||
*/
|
||||
private prepareElementFromStep(step: Driver.Step | string, allSteps: Array<Driver.Step>, stepIndex: number): void;
|
||||
|
||||
/**
|
||||
* Starts presenting the set steps from the given index
|
||||
* @param {number} index
|
||||
*/
|
||||
public start(index?: number): void;
|
||||
|
||||
/**
|
||||
* Highlights the given element. Element can be a query selector or a step definition
|
||||
* @param {string | Driver.Step} element
|
||||
*/
|
||||
public highlight(element: string | Driver.Step): void;
|
||||
}
|
||||
|
||||
namespace Driver {
|
||||
interface Step {
|
||||
/**
|
||||
* Query selector representing the DOM Element
|
||||
*/
|
||||
element: string | HTMLElement | Node;
|
||||
|
||||
/**
|
||||
* Color of stage when this step is active
|
||||
* @default #ffffff
|
||||
*/
|
||||
stageBackground?: string;
|
||||
|
||||
/**
|
||||
* Options representing popover for this step
|
||||
*/
|
||||
popover?: Driver.PopoverOptions;
|
||||
|
||||
/**
|
||||
* Is called when the next element is about to be highlighted
|
||||
*/
|
||||
onNext?: (element: Driver.Element) => void;
|
||||
|
||||
/**
|
||||
* Is called when the previous element is about to be highlighted
|
||||
*/
|
||||
onPrevious?: (element: Driver.Element) => void;
|
||||
}
|
||||
|
||||
class Element {
|
||||
/**
|
||||
* Refers to the DOM element that this class wraps
|
||||
*/
|
||||
private node: Node | HTMLElement;
|
||||
/**
|
||||
* Refers to the global Document object
|
||||
*/
|
||||
private document: Document;
|
||||
/**
|
||||
* Refers to the global window object
|
||||
*/
|
||||
private window: Window;
|
||||
/**
|
||||
* Options for this element
|
||||
*/
|
||||
private options: Driver.ElementOptions;
|
||||
/**
|
||||
* Refers to the overlay that wraps the body
|
||||
*/
|
||||
private overlay: Driver.Overlay;
|
||||
/**
|
||||
* Refers to the Popover object to be displayed against this element
|
||||
*/
|
||||
private popover: Driver.Popover;
|
||||
/**
|
||||
* Refers to the stage that will be displayed behind this element
|
||||
*/
|
||||
private stage: Driver.Stage;
|
||||
|
||||
/**
|
||||
* @param {HTMLElement | Node} node
|
||||
* @param {Driver.DriverOptions} options
|
||||
* @param {Driver.Popover} popover
|
||||
* @param {Driver.Stage} stage
|
||||
* @param {Driver.Overlay} overlay
|
||||
* @param {Window} window
|
||||
* @param {Document} document
|
||||
*/
|
||||
constructor(node: HTMLElement | Node,
|
||||
options: Driver.DriverOptions,
|
||||
popover: Driver.Popover,
|
||||
stage: Driver.Stage,
|
||||
overlay: Driver.Overlay,
|
||||
window: Window,
|
||||
document: Document);
|
||||
|
||||
/**
|
||||
* Checks if the give element is in view port or not
|
||||
* @return {boolean}
|
||||
*/
|
||||
public isInView(): boolean;
|
||||
|
||||
/**
|
||||
* Brings the current DOMElement in view
|
||||
*/
|
||||
public bringInView(): void;
|
||||
|
||||
/**
|
||||
* Gets the position of element on screen
|
||||
* @return {Driver.Position}
|
||||
*/
|
||||
public getCalculatedPosition(): Driver.Position;
|
||||
|
||||
/**
|
||||
* Manually scrolls to current element if scrollInToView is not supported
|
||||
*/
|
||||
private scrollManually(): void;
|
||||
|
||||
/**
|
||||
* Is called when the current element is deselected
|
||||
* @param {boolean} hideStage
|
||||
*/
|
||||
private onDeselected(hideStage?: boolean): void;
|
||||
|
||||
/**
|
||||
* Is called when element is about to be highlighted
|
||||
*/
|
||||
private onHighlightStarted(): void;
|
||||
|
||||
/**
|
||||
* Is called when element has been successfully highlighted
|
||||
*/
|
||||
private onHighlighted(): void;
|
||||
|
||||
/**
|
||||
* Shows the stage on the current element
|
||||
*/
|
||||
private showStage(): void;
|
||||
|
||||
/**
|
||||
* Hides the popover from the current element if visible
|
||||
*/
|
||||
private hidePopover(): void;
|
||||
|
||||
/**
|
||||
* Shows the popover on current element if possible
|
||||
*/
|
||||
private showPopover(): void;
|
||||
|
||||
/**
|
||||
* Gets the full page size
|
||||
*/
|
||||
private getFullPageSize(): Driver.ElementSize;
|
||||
|
||||
/**
|
||||
* Checks if the current element is same as passed element
|
||||
* @param {Driver.Element} element
|
||||
*/
|
||||
private isSame(element: Driver.Element): void;
|
||||
|
||||
/**
|
||||
* Gets the node that this element refers to
|
||||
* @return {Node | HTMLElement}
|
||||
*/
|
||||
public getNode(): Node | HTMLElement;
|
||||
|
||||
/**
|
||||
* Gets the size of current element
|
||||
* @return {Driver.ElementSize}
|
||||
*/
|
||||
public getSize(): Driver.ElementSize;
|
||||
|
||||
/**
|
||||
* Gets the popover on current element if any
|
||||
* @returns {Driver.Popover}
|
||||
*/
|
||||
public getPopover(): Driver.Popover;
|
||||
|
||||
/**
|
||||
* Removes the highlight classes from current element if any
|
||||
*/
|
||||
private removeHighlightClasses(): void;
|
||||
|
||||
/**
|
||||
* Adds the highlight classes to current element if required
|
||||
*/
|
||||
private addHighlightClasses(): void;
|
||||
|
||||
/**
|
||||
* Walks through the parents of the current element and fixes
|
||||
* the stacking context
|
||||
*/
|
||||
private fixStackingContext(): void;
|
||||
|
||||
/**
|
||||
* Checks if we can make the current element relative or not
|
||||
* @return {boolean}
|
||||
*/
|
||||
private canMakeRelative(): boolean;
|
||||
|
||||
/**
|
||||
* Get current element's CSS attribute value
|
||||
* @return {string}
|
||||
*/
|
||||
private getStyleProperty(): string;
|
||||
}
|
||||
|
||||
class Overlay {
|
||||
/**
|
||||
* Options to modify the overlay behavior
|
||||
*/
|
||||
private options: Driver.DriverOptions;
|
||||
|
||||
/**
|
||||
* Refers to currently highlighted element
|
||||
*/
|
||||
private highlightedElement: Driver.Element | null;
|
||||
|
||||
/**
|
||||
* Refers to element highlighted before currently highlighted element
|
||||
*/
|
||||
private lastHighlightedElement: Driver.Element | null;
|
||||
|
||||
/**
|
||||
* Refers to timeout handler used to animate while resetting
|
||||
*/
|
||||
private hideTimer: number | null;
|
||||
|
||||
/**
|
||||
* Refers to global object Window
|
||||
*/
|
||||
private window: Window;
|
||||
|
||||
/**
|
||||
* Refers to global object Document
|
||||
*/
|
||||
private document: Document;
|
||||
|
||||
/**
|
||||
* Prepares the DOM element for overlay and appends to body
|
||||
*/
|
||||
private attachNode(): void;
|
||||
|
||||
/**
|
||||
* Highlights the given Element while resetting the existing one
|
||||
* @param {Driver.Element} element
|
||||
*/
|
||||
public highlight(element: Driver.Element): void;
|
||||
|
||||
/**
|
||||
* Shows the overlay while appending to body if it is not there already
|
||||
*/
|
||||
public show(): void;
|
||||
|
||||
/**
|
||||
* Gets the highlighted element in overlay if any
|
||||
* @return {Driver.Element | null}
|
||||
*/
|
||||
public getHighlightedElement(): Driver.Element | null;
|
||||
|
||||
/**
|
||||
* Gets the element highlighted before current element if any
|
||||
* @return {Driver.Element | null}
|
||||
*/
|
||||
public getLastHighlightedElement(): Driver.Element | null;
|
||||
|
||||
/**
|
||||
* Removes the overlay and deselects the highlighted element. Does that with animation
|
||||
* by default or without animation if immediate is set to false
|
||||
* @param {boolean} immediate
|
||||
*/
|
||||
public clear(immediate?: boolean): void;
|
||||
|
||||
/**
|
||||
* Removes the overlay node if it exists
|
||||
*/
|
||||
private removeNode(): void;
|
||||
|
||||
/**
|
||||
* Refreshes the overlay i.e. sets the size according to current window size
|
||||
* And moves the highlight around if necessary
|
||||
*/
|
||||
public refresh(): void;
|
||||
}
|
||||
|
||||
class Popover {
|
||||
private node: Node | HTMLElement;
|
||||
private tipNode: Node | HTMLElement;
|
||||
private titleNode: Node | HTMLElement;
|
||||
private descriptionNode: Node | HTMLElement;
|
||||
private footerNode: Node | HTMLElement;
|
||||
private nextBtnNode: Node | HTMLElement;
|
||||
private prevBtnNode: Node | HTMLElement;
|
||||
private closeBtnNode: Node | HTMLElement;
|
||||
private window: Window;
|
||||
private document: Document;
|
||||
|
||||
/**
|
||||
* @param {Driver.PopoverOptions} options
|
||||
* @param {Window} window
|
||||
* @param {Document} document
|
||||
*/
|
||||
constructor(options: Driver.PopoverOptions,
|
||||
window: Window,
|
||||
document: Document);
|
||||
|
||||
/**
|
||||
* Prepares the DOM element for popover and appends to the body
|
||||
*/
|
||||
private attachNode(): void;
|
||||
|
||||
/**
|
||||
* Hides the popover if visible
|
||||
*/
|
||||
public hide(): void;
|
||||
|
||||
/**
|
||||
* Sets the initial state for popover before changing position
|
||||
*/
|
||||
private setInitialState(): void;
|
||||
|
||||
/**
|
||||
* Shows the popover at the given position
|
||||
* @param {Driver.Position} position
|
||||
*/
|
||||
public show(position: Driver.Position): void;
|
||||
|
||||
/**
|
||||
* Renders the buttons in the footer of the popover
|
||||
*/
|
||||
private renderFooter(): void;
|
||||
|
||||
/**
|
||||
* Positions the popover to the left of the given element position
|
||||
* @param {Driver.Position} position
|
||||
*/
|
||||
private positionOnLeft(position: Driver.Position): void;
|
||||
|
||||
/**
|
||||
* Positions the popover to the right of the given element position
|
||||
* @param {Driver.Position} position
|
||||
*/
|
||||
private positionOnRight(position: Driver.Position): void;
|
||||
|
||||
/**
|
||||
* Positions the popover to the top of the given element position
|
||||
* @param {Driver.Position} position
|
||||
*/
|
||||
private positionOnTop(position: Driver.Position): void;
|
||||
|
||||
/**
|
||||
* Positions the popover to the bottom of the given element position
|
||||
* @param {Driver.Position} position
|
||||
*/
|
||||
private positionOnBottom(position: Driver.Position): void;
|
||||
|
||||
/**
|
||||
* Positions the popover automatically around the element position
|
||||
* @param {Driver.Position} position
|
||||
*/
|
||||
private autoPosition(position: Driver.Position): void;
|
||||
|
||||
/**
|
||||
* Gets the title node for popover
|
||||
* @returns {Node | HTMLElement}
|
||||
*/
|
||||
public getTitleNode(): Node | HTMLElement;
|
||||
|
||||
/**
|
||||
* Gets the description node for popover
|
||||
* @returns {Node | HTMLElement}
|
||||
*/
|
||||
public getDescriptionNode(): Node | HTMLElement;
|
||||
}
|
||||
|
||||
class Stage extends Element {
|
||||
/**
|
||||
* @param {Driver.StageOptions} options
|
||||
* @param {Window} window
|
||||
* @param {Document} document
|
||||
*/
|
||||
constructor(options: Driver.StageOptions,
|
||||
window: Window,
|
||||
document: Document);
|
||||
|
||||
/**
|
||||
* Prepares the node and appends to body if not there already
|
||||
*/
|
||||
private attachNode(): void;
|
||||
|
||||
/**
|
||||
* Hides the stage by removing the node
|
||||
*/
|
||||
public hide(): void;
|
||||
|
||||
/**
|
||||
* Sets the default properties on the node
|
||||
*/
|
||||
private setInitialStyle(): void;
|
||||
|
||||
/**
|
||||
* Shows the stage at provided position
|
||||
* @param {Driver.Position} position
|
||||
*/
|
||||
public show(position: Driver.Position): void;
|
||||
}
|
||||
|
||||
class Position {
|
||||
constructor({
|
||||
left,
|
||||
top,
|
||||
bottom,
|
||||
right,
|
||||
});
|
||||
|
||||
/**
|
||||
* Checks if the given position is valid and can be highlighted
|
||||
* @return {boolean}
|
||||
*/
|
||||
canHighlight(): boolean;
|
||||
|
||||
/**
|
||||
* Checks if the given position is same as the passed position
|
||||
* @param {Driver.Position} position
|
||||
*/
|
||||
equals(position: Driver.Position): void;
|
||||
}
|
||||
|
||||
interface ScreenCoordinates {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
interface ElementSize {
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
interface PopoverOptions {
|
||||
/**
|
||||
* Title for the popover
|
||||
*/
|
||||
title?: string;
|
||||
|
||||
/**
|
||||
* Description for the popover
|
||||
*/
|
||||
description: string;
|
||||
|
||||
/**
|
||||
* Whether to show control buttons or not
|
||||
* @default true
|
||||
*/
|
||||
showButtons?: boolean;
|
||||
|
||||
/**
|
||||
* Text on the button in the final step
|
||||
* @default 'Done'
|
||||
*/
|
||||
doneBtnText?: string;
|
||||
|
||||
/**
|
||||
* Text on the close button
|
||||
* @default 'Close'
|
||||
*/
|
||||
closeBtnText?: string;
|
||||
|
||||
/**
|
||||
* Text on the next button
|
||||
* @default 'Next'
|
||||
*/
|
||||
nextBtnText?: string;
|
||||
|
||||
/**
|
||||
* Text on the previous button
|
||||
* @default 'Previous'
|
||||
*/
|
||||
prevBtnText?: string;
|
||||
|
||||
/**
|
||||
* Total number of elements with popovers
|
||||
* @default 0
|
||||
*/
|
||||
totalCount?: number;
|
||||
|
||||
/**
|
||||
* Counter for the current popover
|
||||
* @default 0
|
||||
*/
|
||||
currentIndex?: number;
|
||||
|
||||
/**
|
||||
* If the current popover is the first one
|
||||
* @default true
|
||||
*/
|
||||
isFirst?: boolean;
|
||||
|
||||
/**
|
||||
* If the current popover is the last one
|
||||
* @default true
|
||||
*/
|
||||
isLast?: boolean;
|
||||
|
||||
/**
|
||||
* Position for the popover on element
|
||||
* @default auto
|
||||
*/
|
||||
position?: string;
|
||||
}
|
||||
|
||||
interface DriverOptions {
|
||||
/**
|
||||
* Whether to animate while transitioning from one highlighted
|
||||
* element to another
|
||||
* @default true
|
||||
*/
|
||||
animate?: boolean;
|
||||
|
||||
/**
|
||||
* Opacity for the overlay
|
||||
* @default 0.75
|
||||
*/
|
||||
opacity?: number,
|
||||
|
||||
/**
|
||||
* Distance of elements corner from the edges of the overlay
|
||||
* @default 10
|
||||
*/
|
||||
padding?: number,
|
||||
|
||||
/**
|
||||
* Options to be passed to scrollIntoView if supported by browser
|
||||
* @default { behavior: 'instant', block: 'center' }
|
||||
*/
|
||||
scrollIntoViewOptions?: ScrollIntoViewOptions,
|
||||
|
||||
/**
|
||||
* Clicking outside the highlighted element should reset driver or not
|
||||
* @default true
|
||||
*/
|
||||
allowClose?: boolean,
|
||||
|
||||
/**
|
||||
* Whether to allow controlling steps through keyboard
|
||||
* @default true
|
||||
*/
|
||||
keyboardControl?: boolean,
|
||||
|
||||
/**
|
||||
* Clicking outside the highlighted element should move next
|
||||
* @default false
|
||||
*/
|
||||
overlayClickNext?: boolean,
|
||||
|
||||
/**
|
||||
* Background color for the stage behind the highlighted element
|
||||
* @default '#ffffff'
|
||||
*/
|
||||
stageBackground?: string,
|
||||
|
||||
/**
|
||||
* Whether to show control buttons or not
|
||||
* @default true
|
||||
*/
|
||||
showButtons?: boolean;
|
||||
|
||||
/**
|
||||
* Text on the button in the final step
|
||||
* @default 'Done'
|
||||
*/
|
||||
doneBtnText?: string;
|
||||
|
||||
/**
|
||||
* Text on the close button
|
||||
* @default 'Close'
|
||||
*/
|
||||
closeBtnText?: string;
|
||||
|
||||
/**
|
||||
* Text on the next button
|
||||
* @default 'Next'
|
||||
*/
|
||||
nextBtnText?: string;
|
||||
|
||||
/**
|
||||
* Text on the previous button
|
||||
* @default 'Previous'
|
||||
*/
|
||||
prevBtnText?: string;
|
||||
|
||||
/**
|
||||
* Callback to be called when element is about to be highlighted
|
||||
* @param {Driver.Element} element
|
||||
* @returns any
|
||||
*/
|
||||
onHighlightStarted?: (element: Driver.Element) => void;
|
||||
|
||||
/**
|
||||
* Callback to be called when element has been highlighted
|
||||
* @param {Driver.Element} element
|
||||
* @returns any
|
||||
*/
|
||||
onHighlighted?: (element: Driver.Element) => void,
|
||||
|
||||
/**
|
||||
* Callback to be called when element has been deselected
|
||||
* @param {Driver.Element} element
|
||||
* @returns any
|
||||
*/
|
||||
onDeselected?: (element: Driver.Element) => void,
|
||||
|
||||
/**
|
||||
* Is called when the overlay is about to reset
|
||||
*/
|
||||
onReset?: (element: Driver.Element) => void,
|
||||
|
||||
/**
|
||||
* Is called when the next element is about to be highlighted
|
||||
*/
|
||||
onNext?: (element: Driver.Element) => void;
|
||||
|
||||
/**
|
||||
* Is called when the previous element is about to be highlighted
|
||||
*/
|
||||
onPrevious?: (element: Driver.Element) => void;
|
||||
}
|
||||
|
||||
interface ElementOptions extends Driver.DriverOptions {
|
||||
}
|
||||
|
||||
interface StageOptions extends ElementOptions {
|
||||
}
|
||||
}
|
||||
|
||||
export = Driver;
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
const path = require('path');
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
const ExtractTextPlugin = require('extract-text-webpack-plugin');
|
||||
|
||||
const isProduction = process.env.NODE_ENV === 'production';
|
||||
const scriptFileName = 'driver-demo.min.js';
|
||||
const styleFileName = 'driver-demo.min.css';
|
||||
|
||||
module.exports = {
|
||||
mode: isProduction ? 'production' : 'development',
|
||||
entry: [
|
||||
'webpack-dev-server/client?http://localhost:3000',
|
||||
'./demo/styles/demo.scss',
|
||||
'./demo/scripts/emoji.js',
|
||||
'./demo/scripts/demo.js',
|
||||
'./src/index.js',
|
||||
],
|
||||
output: {
|
||||
path: path.join(__dirname, '/dist/demo'),
|
||||
publicPath: '/dist/demo/',
|
||||
filename: scriptFileName,
|
||||
libraryTarget: 'umd',
|
||||
library: 'Driver',
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'eslint-loader',
|
||||
enforce: 'pre',
|
||||
options: {
|
||||
failOnWarning: false,
|
||||
failOnError: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
presets: [
|
||||
[
|
||||
'env',
|
||||
{
|
||||
useBuiltIns: 'usage',
|
||||
},
|
||||
],
|
||||
],
|
||||
plugins: [
|
||||
'babel-plugin-add-module-exports',
|
||||
'transform-object-rest-spread',
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /.scss$/,
|
||||
loader: ExtractTextPlugin.extract([
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: { minimize: isProduction, url: false },
|
||||
},
|
||||
'sass-loader',
|
||||
]),
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new ExtractTextPlugin({
|
||||
filename: styleFileName,
|
||||
allChunks: true,
|
||||
}),
|
||||
new CopyWebpackPlugin([
|
||||
'./demo/images/separator.png',
|
||||
'./demo/images/driver.png',
|
||||
]),
|
||||
],
|
||||
stats: {
|
||||
colors: true,
|
||||
},
|
||||
devtool: 'cheap-module-eval-source-map',
|
||||
};
|
||||
@@ -1,70 +0,0 @@
|
||||
const path = require('path');
|
||||
const ExtractTextPlugin = require('extract-text-webpack-plugin');
|
||||
|
||||
module.exports = {
|
||||
mode: 'production',
|
||||
entry: [
|
||||
'./src/driver.scss',
|
||||
'./src/index.js',
|
||||
],
|
||||
output: {
|
||||
path: path.join(__dirname, '/dist'),
|
||||
publicPath: '/dist/',
|
||||
filename: 'driver.min.js',
|
||||
libraryTarget: 'umd',
|
||||
library: 'Driver',
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'eslint-loader',
|
||||
enforce: 'pre',
|
||||
options: {
|
||||
failOnWarning: false,
|
||||
failOnError: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
presets: [
|
||||
[
|
||||
'env',
|
||||
{
|
||||
useBuiltIns: 'usage',
|
||||
},
|
||||
],
|
||||
],
|
||||
plugins: [
|
||||
'babel-plugin-add-module-exports',
|
||||
'transform-object-rest-spread',
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /.scss$/,
|
||||
loader: ExtractTextPlugin.extract([
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: { minimize: true, url: false },
|
||||
},
|
||||
'sass-loader',
|
||||
]),
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new ExtractTextPlugin({
|
||||
filename: 'driver.min.css',
|
||||
allChunks: true,
|
||||
}),
|
||||
],
|
||||
stats: {
|
||||
colors: true,
|
||||
},
|
||||
devtool: 'cheap-module-source-map',
|
||||
};
|
||||