├── .prettierignore ├── .eslintignore ├── .gitignore ├── src ├── styles │ ├── effects │ │ ├── _variables.scss │ │ ├── _shared.scss │ │ ├── index.scss │ │ ├── _mixins.scss │ │ ├── _fade.scss │ │ ├── _fade-scale.scss │ │ ├── _drop.scss │ │ └── _rotate.scss │ ├── themes │ │ ├── index.scss │ │ ├── _variables.scss │ │ ├── _dark.scss │ │ ├── _light.scss │ │ └── _shared.scss │ └── index.scss ├── helpers │ ├── array.js │ ├── jquery.js │ ├── string.js │ ├── math.js │ └── dom.js ├── api │ ├── globalMethods.js │ ├── autoInit.js │ ├── methods.js │ └── mainFunction.js ├── index.js └── core │ ├── viewport.js │ ├── history.js │ ├── keyboard.js │ ├── ids.js │ ├── instances.js │ ├── classNames.js │ ├── options.js │ └── Skeletabs.js ├── .npmignore ├── docs └── assets │ ├── favicons │ └── favicon.ico │ ├── manifest.json │ ├── highlight │ ├── LICENSE │ └── highlight.pack.js │ ├── script.js │ ├── skeletabs │ └── skeletabs.css │ └── style.css ├── .editorconfig ├── .vscode └── settings.json ├── .prettierrc ├── LICENSE ├── .eslintrc ├── test ├── index.css ├── index.js └── index.html ├── package.json ├── webpack.config.js ├── CHANGELOG.md ├── README.md └── dist ├── skeletabs.css └── skeletabs.js /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | node_modules/ 3 | test/lib/**/* 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | Thumbs.db 3 | .DS_Store 4 | *.log -------------------------------------------------------------------------------- /src/styles/effects/_variables.scss: -------------------------------------------------------------------------------- 1 | $duration: 500ms !default; 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .git 2 | .vscode 3 | docs 4 | node_modules 5 | test 6 | *.log -------------------------------------------------------------------------------- /src/styles/themes/index.scss: -------------------------------------------------------------------------------- 1 | @import 'variables'; 2 | @import 'shared'; 3 | @import 'light'; 4 | @import 'dark'; 5 | -------------------------------------------------------------------------------- /docs/assets/favicons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/findawayer/skeletabs/HEAD/docs/assets/favicons/favicon.ico -------------------------------------------------------------------------------- /src/styles/index.scss: -------------------------------------------------------------------------------- 1 | /*! Skeletabs v{{version}}: bundled themes and effects | MIT License */ 2 | @import 'themes'; 3 | @import 'effects'; 4 | -------------------------------------------------------------------------------- /src/helpers/array.js: -------------------------------------------------------------------------------- 1 | // Array.prototype.includes from ES2017 2 | export function includes(array, item) { 3 | return array.indexOf(item) !== -1; 4 | } 5 | -------------------------------------------------------------------------------- /src/styles/effects/_shared.scss: -------------------------------------------------------------------------------- 1 | // only works in tabs mode 2 | .skltbs-mode-tabs { 3 | .skltbs-panel-group { 4 | position: relative; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/styles/effects/index.scss: -------------------------------------------------------------------------------- 1 | @import 'variables'; 2 | @import 'mixins'; 3 | @import 'shared'; 4 | @import 'fade'; 5 | @import 'fade-scale'; 6 | @import 'drop'; 7 | @import 'rotate'; 8 | -------------------------------------------------------------------------------- /src/helpers/jquery.js: -------------------------------------------------------------------------------- 1 | // Call jQuery.fn.each backwards on jQuery wrapped elements 2 | export function reverseEach($elements, callback) { 3 | $elements.pushStack($elements.get().reverse()).each(callback); 4 | } 5 | -------------------------------------------------------------------------------- /src/helpers/string.js: -------------------------------------------------------------------------------- 1 | // Make string start with an uppercase letter and the rest be lowercase 2 | export function capitalize(string) { 3 | return string[0].toUpperCase() + string.slice(1).toLowerCase(); 4 | } 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = crlf 7 | indent_size = 2 8 | indent_style = space 9 | insert_final_newline = true 10 | max_line_length = 80 11 | trim_trailing_whitespace = true -------------------------------------------------------------------------------- /src/helpers/math.js: -------------------------------------------------------------------------------- 1 | // Modulo operation 2 | export function modulo(x, max) { 3 | return ((x % max) + max) % max; 4 | } 5 | 6 | // Test if number `x` is out of `min` ~ `max` range 7 | export function inRange(x, min, max) { 8 | return min <= x && x <= max; 9 | } 10 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "editor.defaultFormatter": "esbenp.prettier-vscode", 4 | "[javascript]": { 5 | "editor.formatOnSave": false, 6 | "editor.codeActionsOnSave": { 7 | "source.fixAll.eslint": true 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/api/globalMethods.js: -------------------------------------------------------------------------------- 1 | import { setClassNames } from '../core/classNames'; 2 | import { setDefaults } from '../core/options'; 3 | 4 | // Methods that affects all Skeletabs instances. 5 | // @example: $.skeletabs.setDefaults(myDefaults); 6 | export default { 7 | setClassNames, 8 | setDefaults, 9 | }; 10 | -------------------------------------------------------------------------------- /src/styles/effects/_mixins.scss: -------------------------------------------------------------------------------- 1 | // 1. Limit transition to tabs mode 2 | // 2. prepend `use-` to the effect name 3 | @mixin effect($name) { 4 | .skltbs-mode-tabs.use-#{$name} { 5 | @content; 6 | } 7 | } 8 | 9 | // Override `style` attribute added by JS 10 | // we need the leaving panel to stay visible to apply effects. 11 | @mixin force-visible { 12 | display: block !important; 13 | } 14 | -------------------------------------------------------------------------------- /docs/assets/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Skeletabs", 3 | "name": "Skeletabs documentation", 4 | "description": "Basic, accessible, responsive and freely restyleable tabs.", 5 | "icons": [ 6 | { 7 | "src": "favicon.ico", 8 | "sizes": "64x64 32x32 24x24 16x16", 9 | "type": "image/x-icon" 10 | } 11 | ], 12 | "homepage_url": "https://findawayer.github.io/Skeletabs/" 13 | } 14 | -------------------------------------------------------------------------------- /src/styles/effects/_fade.scss: -------------------------------------------------------------------------------- 1 | @include effect('fade') { 2 | .skltbs-panel { 3 | position: relative; 4 | transition: opacity $duration; 5 | } 6 | 7 | .skltbs-leave { 8 | position: absolute; 9 | top: 0; 10 | @include force-visible; 11 | opacity: 1; 12 | 13 | &-active { 14 | opacity: 0; 15 | } 16 | } 17 | 18 | .skltbs-enter { 19 | opacity: 0; 20 | 21 | &-active { 22 | opacity: 1; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "useTabs": false, 4 | "semi": true, 5 | "singleQuote": true, 6 | "endOfLine": "auto", 7 | "trailingComma": "es5", 8 | "arrowParens": "avoid", 9 | "overrides": [ 10 | { 11 | "files": "*.html", 12 | "options": { 13 | "printWidth": 120 14 | } 15 | }, 16 | { 17 | "files": ["docs/**/*.js", "test/**/*.js"], 18 | "options": { 19 | "trailingComma": "none" 20 | } 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /src/styles/themes/_variables.scss: -------------------------------------------------------------------------------- 1 | $color-light-background: #fff !default; 2 | $color-light-accent: #2b8ff5 !default; 3 | $color-light-text: #333 !default; 4 | $color-light-fade: #f6f6f8 !default; 5 | 6 | $color-dark-background: #1a1b1c !default; 7 | $color-dark-accent: #2b8ff5 !default; 8 | $color-dark-text: #fff !default; 9 | $color-dark-fade: #2d2e2f !default; 10 | 11 | $tab-padding: 1em 1.25em !default; 12 | $panel-padding: 15px 25px !default; 13 | $border-spacing: 3px !default; 14 | $outline-width: 2px !default; 15 | -------------------------------------------------------------------------------- /src/styles/effects/_fade-scale.scss: -------------------------------------------------------------------------------- 1 | @include effect('fade-scale') { 2 | .skltbs-panel { 3 | position: relative; 4 | transition: $duration; 5 | transition-property: opacity, transform; 6 | } 7 | 8 | .skltbs-leave { 9 | position: absolute; 10 | top: 0; 11 | @include force-visible; 12 | opacity: 1; 13 | transform: scale(1); 14 | 15 | &-active { 16 | opacity: 0; 17 | transform: scale(0.95); 18 | } 19 | } 20 | 21 | .skltbs-enter { 22 | opacity: 0; 23 | transform: scale(0.95); 24 | 25 | &-active { 26 | opacity: 1; 27 | transform: scale(1); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/styles/effects/_drop.scss: -------------------------------------------------------------------------------- 1 | @include effect('drop') { 2 | .skltbs-panel { 3 | position: relative; 4 | transition: $duration; 5 | transition-property: opacity, transform; 6 | } 7 | 8 | .skltbs-leave { 9 | position: absolute; 10 | top: 0; 11 | @include force-visible; 12 | opacity: 1; 13 | transform: translate3d(0, 0, 0); 14 | 15 | &-active { 16 | opacity: 0; 17 | transform: translate3d(0, 50%, 0); 18 | } 19 | } 20 | 21 | .skltbs-enter { 22 | opacity: 0; 23 | transform: scale(0.5); 24 | 25 | &-active { 26 | opacity: 1; 27 | transform: scale(1); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /*! Skeletabs v{{version}} | MIT License | Requires jQuery v1 or higher */ 2 | import globalMethods from './api/globalMethods'; 3 | import mainFunction from './api/mainFunction'; 4 | import autoInit from './api/autoInit'; 5 | import './styles/index.scss'; 6 | 7 | if (typeof $ === 'undefined') { 8 | throw new ReferenceError('Skeletabs requires jQuery to be loaded.'); 9 | } 10 | 11 | // $(element).skeletabs(); 12 | $.fn.extend({ 13 | skeletabs: mainFunction, 14 | }); 15 | 16 | // $.skeletabs.globalMethod(); 17 | $.skeletabs = { 18 | version: '{{version}}', 19 | ...globalMethods, 20 | }; 21 | 22 | // Auto init on elements matching given selector on DOM ready 23 | $(function () { 24 | autoInit(); 25 | }); 26 | -------------------------------------------------------------------------------- /src/styles/effects/_rotate.scss: -------------------------------------------------------------------------------- 1 | @include effect('rotate') { 2 | .skltbs-panel-group { 3 | overflow: hidden; 4 | } 5 | 6 | .skltbs-panel { 7 | position: relative; 8 | transition: $duration; 9 | transition-property: opacity, transform; 10 | } 11 | 12 | .skltbs-leave { 13 | position: absolute; 14 | top: 0; 15 | @include force-visible; 16 | opacity: 1; 17 | transform: translate3d(0, 0, 0); 18 | 19 | &-active { 20 | opacity: 0; 21 | transform: translate3d(-50%, 0, 0); 22 | } 23 | } 24 | 25 | .skltbs-enter { 26 | opacity: 0; 27 | transform: translate3d(50%, 0, 0); 28 | 29 | &-active { 30 | opacity: 1; 31 | transform: translate3d(0, 0, 0); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/core/viewport.js: -------------------------------------------------------------------------------- 1 | // Cached viewport size 2 | let viewportWidth; 3 | 4 | // Update viewport width 5 | function updateViewportWidth() { 6 | viewportWidth = $(window).width(); 7 | } 8 | 9 | // Keep tracking viewport size changes. 10 | // (Doesn't seem to need `debounce` because the callback function is not costly.) 11 | function watchViewportWidth() { 12 | $(window).on('resize orientationchange', updateViewportWidth); 13 | } 14 | 15 | // Get viewport width 16 | function getViewportWidth() { 17 | // Lazy watch viewport when first Skeletabs instance is created 18 | if (typeof viewportWidth === 'undefined') { 19 | updateViewportWidth(); 20 | watchViewportWidth(); 21 | } 22 | return viewportWidth; 23 | } 24 | 25 | export { getViewportWidth, watchViewportWidth }; 26 | -------------------------------------------------------------------------------- /src/core/history.js: -------------------------------------------------------------------------------- 1 | // Check if history API is availalble 2 | function supportsHistory() { 3 | return !!(window.history && window.history.pushState); 4 | } 5 | 6 | // Change current history entry 7 | function replaceHistory(hash) { 8 | if (supportsHistory()) { 9 | window.history.replaceState(null, null, hash); 10 | } 11 | } 12 | 13 | // Create a new history entry (allows navigating back and forth) 14 | function pushHistory(hash) { 15 | if (supportsHistory()) { 16 | window.history.pushState({ hash }, null, hash); 17 | } 18 | } 19 | 20 | // Get hash value from a jQuery wrapped `popstate` event 21 | function getHashFromHistory({ originalEvent: { state } }) { 22 | return state && state.hash ? state.hash : null; 23 | } 24 | 25 | export { supportsHistory, pushHistory, replaceHistory, getHashFromHistory }; 26 | -------------------------------------------------------------------------------- /src/api/autoInit.js: -------------------------------------------------------------------------------- 1 | import { createInstance, register } from '../core/instances'; 2 | import { reverseEach } from '../helpers/jquery'; 3 | 4 | // Automatically instantiate Skeletabs on container elements with `data-skeletabs` attribute 5 | // and use the value of `data-skeletabs` and `skeletabs-class` attributes 6 | // as user options and classNames respectively. 7 | export default function autoInit() { 8 | const $containers = $('[data-skeletabs]'); 9 | reverseEach($containers, function (_, container) { 10 | // Get user options from `data-skeletabs` attribute 11 | const $container = $(container); 12 | const options = $container.data('skeletabs'); 13 | const classNames = $container.data('skeletabs-class'); 14 | // Create new Skeletabs instance 15 | const instance = createInstance(container, options, classNames); 16 | // Store reference to the instance 17 | register(container, instance); 18 | // Initialize 19 | instance.init(); 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /src/core/keyboard.js: -------------------------------------------------------------------------------- 1 | // List of expected key inputs 2 | const expectedKeys = { 3 | // Desktop keyboard 4 | 38: 'up', 5 | 40: 'down', 6 | 37: 'left', 7 | 39: 'right', 8 | // Home/End 9 | 36: 'home', 10 | 35: 'end', 11 | // WSAD keys 12 | 87: 'up', 13 | 83: 'down', 14 | 65: 'left', 15 | 68: 'right', 16 | // Numpad 17 | 104: 'up', 18 | 98: 'down', 19 | 100: 'left', 20 | 102: 'right', 21 | }; 22 | 23 | // Action map 24 | const actions = { 25 | horizontal: { 26 | left: 'prev', 27 | right: 'next', 28 | }, 29 | vertical: { 30 | up: 'prev', 31 | down: 'next', 32 | }, 33 | common: { 34 | home: 'first', 35 | end: 'last', 36 | }, 37 | }; 38 | 39 | // Get action name from a KeyboardEvent 40 | function parseKeyAction(event, direction) { 41 | const keycode = event.which || event.keyCode; 42 | const key = expectedKeys[keycode]; 43 | if (!key) { 44 | return null; 45 | } 46 | return actions[direction][key] || actions.common[key] || null; 47 | } 48 | 49 | export { parseKeyAction }; 50 | -------------------------------------------------------------------------------- /src/styles/themes/_dark.scss: -------------------------------------------------------------------------------- 1 | // container 2 | .skltbs-theme-dark { 3 | color: $color-dark-text; 4 | 5 | .skltbs { 6 | // tabs 7 | &-tab { 8 | background: $color-dark-fade; 9 | 10 | // focused tab 11 | &:focus { 12 | outline: 0; 13 | box-shadow: inset 0 0 0 $outline-width darken($color-dark-accent, 20); 14 | } 15 | 16 | // disabled tabs 17 | &:disabled { 18 | color: lighten($color-dark-text, 60); 19 | background: transparent; 20 | } 21 | 22 | // active tab 23 | &.skltbs-active { 24 | background: $color-dark-accent; 25 | 26 | &:focus, 27 | &:hover { 28 | border-color: darken($color-dark-accent, 70); 29 | } 30 | } 31 | } 32 | 33 | // contents to toggle 34 | &-panel { 35 | background-color: $color-dark-background; 36 | border: 2px solid $color-dark-fade; 37 | 38 | &:focus { 39 | outline: 0; 40 | border-color: darken($color-dark-accent, 20); 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/styles/themes/_light.scss: -------------------------------------------------------------------------------- 1 | // container 2 | .skltbs-theme-light { 3 | color: $color-light-text; 4 | 5 | .skltbs { 6 | // tabs 7 | &-tab { 8 | background: $color-light-fade; 9 | 10 | // focused tab 11 | &:focus { 12 | outline: 0; 13 | box-shadow: inset 0 0 0 $outline-width darken($color-light-accent, 15); 14 | } 15 | 16 | // disabled tabs 17 | &:disabled { 18 | color: lighten($color-light-text, 60); 19 | background: transparent; 20 | } 21 | 22 | // active tab 23 | &.skltbs-active { 24 | color: white; 25 | background: $color-light-accent; 26 | 27 | &:focus, 28 | &:hover { 29 | border-color: lighten($color-light-accent, 10); 30 | } 31 | } 32 | } 33 | 34 | // contents to toggle 35 | &-panel { 36 | background-color: $color-light-background; 37 | border: 2px solid $color-light-fade; 38 | 39 | &:focus { 40 | outline: 0; 41 | border-color: lighten($color-light-accent, 15); 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Tae Sung, Lim 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/core/ids.js: -------------------------------------------------------------------------------- 1 | // Id map 2 | const ids = {}; 3 | 4 | // Create unique id of same `type` and return it `prefix`-ed. 5 | function nextId({ type, prefix = '' }) { 6 | ids[type] = (ids[type] || 0) + 1; 7 | return prefix + ids[type]; 8 | } 9 | 10 | // Use UNIX timestamp as id 11 | function getTimeId() { 12 | return Date.now(); 13 | } 14 | 15 | // Make sure all tabs/panels have an id; 16 | // if one of them is missing id, assign a newly created one 17 | function setIds($elements, options) { 18 | return $elements 19 | .map((_, element) => { 20 | if (!element.id) { 21 | element.id = nextId(options); 22 | // Use `originalId` attribute to detect the id was made up. 23 | // Needed for destroy() process. 24 | element.dynamicId = true; 25 | } 26 | return element.id; 27 | }) 28 | .get(); 29 | } 30 | 31 | // Clean up generated ids 32 | function unsetIds($elements) { 33 | $elements.each(function (_, element) { 34 | if (element.dynamicId) { 35 | element.removeAttribute('id'); 36 | delete element.dynamicId; 37 | } 38 | }); 39 | } 40 | 41 | export { nextId, getTimeId, setIds, unsetIds }; 42 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true, 5 | "jquery": true 6 | }, 7 | "parserOptions": { 8 | "ecmaVersion": 2018, 9 | "sourceType": "module" 10 | }, 11 | "extends": [ 12 | // eslint-config-airbnb-base: https://github.com/airbnb/javascript 13 | "airbnb-base", 14 | // eslint-plugin-prettier + eslint-config-prettier 15 | "plugin:prettier/recommended" 16 | ], 17 | "rules": { 18 | "consistent-return": ["error", { "treatUndefinedAsUnspecified": true }], 19 | "func-names": "off", 20 | "no-console": "off", 21 | "no-param-reassign": ["error", { "props": false }], 22 | "no-unused-expressions": [ 23 | "error", 24 | { "allowShortCircuit": true, "allowTernary": true } 25 | ], 26 | "import/prefer-default-export": "off" 27 | }, 28 | "overrides": [ 29 | { 30 | "files": ["docs/**/*.js", "test/**/*.js"], 31 | "env": { 32 | "es6": false 33 | }, 34 | "parserOptions": { 35 | "ecmaVersion": 5, 36 | "sourceType": "script" 37 | }, 38 | "extends": ["eslint:recommended", "plugin:prettier/recommended"], 39 | "rules": { 40 | "no-undef": "off", 41 | "no-use-before-define": "off", 42 | "no-var": "off", 43 | "object-shorthand": "off" 44 | } 45 | } 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /src/core/instances.js: -------------------------------------------------------------------------------- 1 | import Skeletabs from './Skeletabs'; 2 | 3 | const registered = {}; 4 | 5 | // Test if passed element has created a Skeletabs instance 6 | function hasInstance(container) { 7 | return typeof container.skeletabsId !== 'undefined'; 8 | } 9 | 10 | // Create Skeletabs instance 11 | function createInstance(container, options, classNames) { 12 | if (hasInstance(container)) { 13 | throw new Error('Skeletabs has already been initialized.'); 14 | } 15 | return new Skeletabs(container, options, classNames); 16 | } 17 | 18 | // Get Skeletabs instance using the reference stored inside the `container`. 19 | function getInstance(container) { 20 | const id = container.skeletabsId; 21 | return id ? registered[id] || null : null; 22 | } 23 | 24 | // Create reference to the `instance` and store it inside the `element`. 25 | // This allows us to retrieve the instance later, while keeping it private from the client. 26 | function register(container, instance) { 27 | registered[instance.id] = instance; 28 | container.skeletabsId = instance.id; 29 | } 30 | 31 | // Remove references to the instance 32 | function unregister(instance) { 33 | const { id, container } = instance; 34 | if (id in registered) { 35 | delete container.skeletabsId; 36 | delete registered[id]; 37 | } 38 | } 39 | 40 | export { createInstance, getInstance, hasInstance, register, unregister }; 41 | -------------------------------------------------------------------------------- /src/styles/themes/_shared.scss: -------------------------------------------------------------------------------- 1 | // container 2 | .skltbs-theme-light, 3 | .skltbs-theme-dark { 4 | .skltbs { 5 | // tab group 6 | &-tab-group { 7 | margin: 0; 8 | padding: 0; 9 | list-style: none; 10 | 11 | // self clear floats 12 | &::after { 13 | content: ''; 14 | clear: both; 15 | display: table; 16 | } 17 | } 18 | 19 | // tab items 20 | &-tab-item { 21 | float: left; 22 | margin: 0 $border-spacing $border-spacing 0; 23 | } 24 | 25 | // tabs 26 | &-tab { 27 | display: block; 28 | padding: $tab-padding; 29 | color: inherit; 30 | border: 0; 31 | font-family: inherit; 32 | font-size: 1rem; 33 | appearance: none; 34 | user-select: none; 35 | touch-action: manipulation; 36 | 37 | // disabled tabs 38 | &:disabled { 39 | background: transparent; 40 | cursor: not-allowed; 41 | touch-action: none; 42 | } 43 | } 44 | 45 | // contents to toggle 46 | &-panel { 47 | padding: $panel-padding; 48 | border: 2px solid; 49 | 50 | // accordion triggers 51 | &-heading { 52 | margin-top: $border-spacing; 53 | 54 | &:first-child { 55 | margin-top: 0; 56 | } 57 | 58 | .skltbs-tab { 59 | width: 100%; 60 | } 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /test/index.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-size: 16px; 3 | } 4 | 5 | body { 6 | margin: 30px 20px; 7 | } 8 | 9 | .drawer { 10 | position: relative; 11 | max-width: 1200px; 12 | height: 100%; 13 | margin: 0 auto; 14 | padding-right: 260px; 15 | color: #333; 16 | line-height: 1.7em; 17 | } 18 | 19 | .control { 20 | position: fixed; 21 | top: 30px; 22 | right: 20px; 23 | width: 240px; 24 | } 25 | 26 | .control-row { 27 | margin-bottom: 10px; 28 | } 29 | 30 | .control-label { 31 | display: block; 32 | margin-bottom: 0.25em; 33 | } 34 | 35 | .control-select { 36 | display: block; 37 | width: 100%; 38 | padding: 0.5em 0.25em; 39 | font: inherit; 40 | border: 1px solid #aaa; 41 | } 42 | 43 | .control-dump { 44 | min-height: 5em; 45 | padding: 10px; 46 | background-color: #f4f4f6; 47 | font-size: 15px; 48 | word-wrap: break-word; 49 | } 50 | 51 | .control-button { 52 | display: block; 53 | width: 100%; 54 | padding: 0.5em 0.25em; 55 | color: #fff; 56 | background: #333; 57 | border: 0; 58 | font: inherit; 59 | text-align: center; 60 | text-transform: uppercase; 61 | appearance: none; 62 | } 63 | 64 | .control-button:focus, 65 | .control-button:hover { 66 | background: #2b8ff5; 67 | outline: 0; 68 | } 69 | 70 | @media (max-width: 768px) { 71 | body { 72 | margin: 20px; 73 | } 74 | 75 | .drawer { 76 | padding-right: 0; 77 | } 78 | 79 | .control { 80 | position: static; 81 | top: auto; 82 | right: auto; 83 | width: auto; 84 | max-width: 480px; 85 | margin: 20px auto 0; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /docs/assets/highlight/LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2006, Ivan Sagalaev. 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /src/core/classNames.js: -------------------------------------------------------------------------------- 1 | // Default classNames 2 | const defaults = { 3 | tabGroup: 'skltbs-tab-group', 4 | tabItem: 'skltbs-tab-item', 5 | tab: 'skltbs-tab', 6 | panelGroup: 'skltbs-panel-group', 7 | panel: 'skltbs-panel', 8 | panelHeading: 'skltbs-panel-heading', 9 | init: 'skltbs-init', 10 | tabsMode: 'skltbs-mode-tabs', 11 | accordionMode: 'skltbs-mode-accordion', 12 | active: 'skltbs-active', 13 | disabled: 'skltbs-disabled', 14 | enter: 'skltbs-enter', 15 | enterActive: 'skltbs-enter-active', 16 | enterDone: 'skltbs-enter-done', 17 | leave: 'skltbs-leave', 18 | leaveActive: 'skltbs-leave-active', 19 | leaveDone: 'skltbs-leave-done', 20 | }; 21 | 22 | // Copy default classNames 23 | let classNames = $.extend({}, defaults); 24 | 25 | // Get a copy of defaults with each value prefixed by `userPrefix` 26 | function getPrefixedClassNames(userPrefix) { 27 | return Object.keys(defaults).reduce((prefixed, key, index, array) => { 28 | prefixed[key] = defaults[key].replace('skltbs', userPrefix); 29 | return prefixed; 30 | }, {}); 31 | } 32 | 33 | // Get classNames object 34 | function getClassNames(arg) { 35 | switch (typeof arg) { 36 | case 'object': 37 | return $.extend({}, classNames, arg); 38 | case 'string': 39 | return getPrefixedClassNames(arg); 40 | default: 41 | return classNames; 42 | } 43 | } 44 | 45 | // Configure custom classNames 46 | function setClassNames(arg) { 47 | if (typeof arg === 'object') { 48 | $.extend(classNames, arg); 49 | } 50 | if (typeof arg === 'string') { 51 | classNames = getPrefixedClassNames(arg); 52 | } 53 | throw new Error('setClassNames requires an object or a string as argument.'); 54 | } 55 | 56 | export { getClassNames, setClassNames }; 57 | -------------------------------------------------------------------------------- /src/core/options.js: -------------------------------------------------------------------------------- 1 | // Default options 2 | const defaults = { 3 | autoplay: false, 4 | autoplayInterval: 3000, 5 | breakpoint: 640, 6 | breakpointLayout: 'accordion', 7 | disabledIndex: null, 8 | history: 'replace', // 'replace' | 'push' | false 9 | keyboard: 'select', // 'select' | 'focus' | false 10 | keyboardAccordion: 'vertical', 11 | keyboardTabs: 'horizontal', 12 | panelHeight: 'auto', // 'auto' | 'equal' | 'adaptive' 13 | pauseOnFocus: true, 14 | pauseOnHover: false, 15 | resizeTimeout: 100, 16 | selectEvent: 'click', // 'click' | 'hover' 17 | slidingAccordion: false, 18 | startIndex: 0, 19 | transitionDuration: 500, 20 | }; 21 | 22 | let options = defaults; 23 | 24 | // Custom processor for merging values 25 | // - undefined, null: ignore 26 | // - true: override only if the default type is boolean 27 | // - false or other types: override 28 | function mergeOptions(targetObject, sourceObject) { 29 | if (!sourceObject) { 30 | return targetObject; 31 | } 32 | if (typeof sourceObject !== 'object') { 33 | throw new Error('Options should be an object type.'); 34 | } 35 | let target; 36 | let source; 37 | return Object.keys(targetObject).reduce((merged, key) => { 38 | target = targetObject[key]; 39 | source = sourceObject[key]; 40 | if (source === undefined || source === null) { 41 | merged[key] = target; 42 | } else if (source === true) { 43 | merged[key] = typeof target === 'boolean' ? source : target; 44 | } else { 45 | merged[key] = source; 46 | } 47 | return merged; 48 | }, {}); 49 | } 50 | 51 | // Config custom classnames 52 | function setDefaults(userOptions) { 53 | options = mergeOptions(defaults, userOptions); 54 | } 55 | 56 | // Merge user options with defaults 57 | function processOptions(userOptions) { 58 | return mergeOptions(options, userOptions); 59 | } 60 | 61 | export { setDefaults, processOptions }; 62 | -------------------------------------------------------------------------------- /src/api/methods.js: -------------------------------------------------------------------------------- 1 | import { getInstance, unregister } from '../core/instances'; 2 | 3 | // List of supported queries inside .skeletabs() calls 4 | // (The context is the skeletabs instance matching passed container element) 5 | const api = { 6 | destroy() { 7 | this.destroy(); 8 | unregister(this); 9 | }, 10 | reload() { 11 | this.reload(); 12 | }, 13 | goTo(query) { 14 | switch (typeof query) { 15 | case 'number': 16 | this.goTo(query, { updateHistory: true }); 17 | break; 18 | 19 | case 'string': 20 | if (query[0] === '#') { 21 | this.goTo(this.getIndexByHash(query), { updateHistory: false }); 22 | } 23 | break; 24 | 25 | default: 26 | throw Error(`Invalid parameter: ${JSON.stringify(query)}`); 27 | } 28 | }, 29 | next() { 30 | this.go(1, { updateHistory: true }); 31 | }, 32 | prev() { 33 | this.go(-1, { updateHistory: true }); 34 | }, 35 | play() { 36 | this.play(); 37 | }, 38 | pause() { 39 | this.pause(); 40 | }, 41 | add(data) { 42 | this.add(data); 43 | }, 44 | remove(index) { 45 | this.remove(index); 46 | }, 47 | getCurrentInfo() { 48 | return this.getCurrentInfo(); 49 | }, 50 | }; 51 | 52 | // Process user queries and execute relevant method 53 | function call(command, container, params) { 54 | const instance = getInstance(container); 55 | // Method doesn't exist 56 | if (!Object.prototype.hasOwnProperty.call(api, command)) { 57 | throw new Error(`Invalid method name: ${command}`); 58 | } 59 | // Skeletabs has not been initialized 60 | if (!instance) { 61 | throw new Error( 62 | `Skeletabs is not initialized on element: ${ 63 | container.id ? `#${container.id}` : container.className 64 | }` 65 | ); 66 | } 67 | return api[command].apply(instance, params); 68 | } 69 | 70 | export { call }; 71 | export default api; 72 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "skeletabs", 3 | "version": "2.1.3-alpha.0", 4 | "description": "Basic, accessible, responsive and freely restyleable tabs.", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "build": "cross-env NODE_ENV=production webpack", 8 | "dev": "cross-env NODE_ENV=development webpack-dev-server", 9 | "eslint": "eslint --fix --ext .js" 10 | }, 11 | "files": [ 12 | "dist/skeletabs.js", 13 | "dist/skeletabs.css" 14 | ], 15 | "keywords": [ 16 | "ui", 17 | "tabs", 18 | "responsive", 19 | "javascript", 20 | "jquery", 21 | "jquery-plugin", 22 | "plugin" 23 | ], 24 | "author": "Tae Sung Lim ", 25 | "license": "MIT", 26 | "repository": "https://github.com/findawayer/Skeletabs.git", 27 | "bugs": { 28 | "url": "https://github.com/findawayer/Skeletabs/issues" 29 | }, 30 | "devDependencies": { 31 | "@babel/core": "^7.11.1", 32 | "@babel/preset-env": "^7.11.0", 33 | "autoprefixer": "^9.8.6", 34 | "babel-loader": "^8.1.0", 35 | "cross-env": "^7.0.3", 36 | "css-loader": "^4.2.1", 37 | "eslint": "7.2.0", 38 | "eslint-config-airbnb-base": "14.2.0", 39 | "eslint-config-prettier": "^6.11.0", 40 | "eslint-plugin-import": "2.21.2", 41 | "eslint-plugin-prettier": "^3.1.4", 42 | "filemanager-webpack-plugin": "^3.0.0-alpha.7", 43 | "mini-css-extract-plugin": "^0.10.0", 44 | "postcss-loader": "^3.0.0", 45 | "prettier": "^2.0.5", 46 | "sass": "^1.26.10", 47 | "sass-loader": "^9.0.3", 48 | "string-replace-loader": "^2.3.0", 49 | "style-loader": "^1.2.1", 50 | "uglifyjs-webpack-plugin": "^2.2.0", 51 | "webpack": "^4.44.1", 52 | "webpack-cli": "^3.3.12", 53 | "webpack-dev-server": "^3.11.0" 54 | }, 55 | "dependencies": { 56 | "lodash.debounce": "^4.0.8" 57 | }, 58 | "peerDependencies": { 59 | "jquery": "^1.12.0" 60 | }, 61 | "babel": { 62 | "presets": [ 63 | "@babel/preset-env" 64 | ] 65 | }, 66 | "browserslist": [ 67 | "last 2 versions", 68 | "> 1%", 69 | "ie 9" 70 | ] 71 | } 72 | -------------------------------------------------------------------------------- /src/api/mainFunction.js: -------------------------------------------------------------------------------- 1 | import { call } from './methods'; 2 | import { createInstance, register } from '../core/instances'; 3 | import { reverseEach } from '../helpers/jquery'; 4 | 5 | /** 6 | * jQuery.fn.skeletabs 7 | * 8 | * Invoke skeletabs based on the syntax: 9 | * .skeletabs(string) -> call the public method 10 | * .skeletabs() or .skeletabs(options) -> create new Skeletabs instance 11 | * 12 | * There are 2 reasons why this should be done in reverse order: 13 | * - To support nested Skeletabs instances. 14 | * There are times we need to calculate the final size of a panel (e.g. { panelHeight: 'equal' }) 15 | * which is possible only after the inner instance completes its initialization. 16 | * - To return the first value after calling a getter method. 17 | * `returnValue` is overwritten in each iteration and returns the last value in the end; 18 | * however, it would be more consistent to represent the result from the first match. 19 | */ 20 | export default function skeletabs(firstArg, ...restArgs) { 21 | let returnValue; 22 | 23 | // [production] Wrap in try catch block so internal `throw` statements 24 | // do not break the whole script execution 25 | // [development] Don't try/catch because we need to track down the error source 26 | // @dev-only try { 27 | reverseEach(this, function (_, container) { 28 | // .skeletabs(method) => call(method) 29 | // .skeletabs() or .skeletabs(options) => new Skeletabs(options) 30 | if (typeof firstArg === 'string') { 31 | returnValue = call(firstArg, container, restArgs); 32 | } else { 33 | // firstArg -> options, secondArg -> classNames 34 | const instance = createInstance(container, firstArg, restArgs[0]); 35 | register(container, instance); 36 | instance.init(); 37 | } 38 | }); 39 | // @dev-only } catch (error) { 40 | // Thrown errors will be logged in the console 41 | // @dev-only console.error(error.message); 42 | // @dev-only } 43 | 44 | // Return the initial jQuery object to support chaining 45 | // unless user wants to get data from an skeletabs instance. 46 | return returnValue || this; 47 | } 48 | -------------------------------------------------------------------------------- /src/helpers/dom.js: -------------------------------------------------------------------------------- 1 | // Check if passed element occupies at least 1x1 px. 2 | // (https://github.com/jquery/jquery/blob/d0ce00cdfa680f1f0c38460bc51ea14079ae8b07/src/css/hiddenVisibleSelectors.js) 3 | function isOccupyingSpace(element) { 4 | return !!( 5 | element.offsetWidth || 6 | element.offsetHeight || 7 | element.getClientRects().length 8 | ); 9 | } 10 | 11 | // Check if 12 | // 1. They have a CSS display value of none. 13 | // 2. Their width and height are explicitly set to 0. 14 | function isExplicitelyHidden(element) { 15 | const { display, width, height } = element.style; 16 | return display === 'none' || (width === '0' && height === '0'); 17 | } 18 | 19 | // Find out which element in the DOM tree is hiding the passed element. 20 | export function findHiddenInTree(element) { 21 | // The element is explicetely set hidden by CSS declarations. 22 | if (isExplicitelyHidden(element)) { 23 | return element; 24 | } 25 | // We consider the element visible if it takes up space. 26 | if (isOccupyingSpace(element)) { 27 | return null; 28 | } 29 | // We can't search further as the currnet element is the top level element. 30 | if (element.parentElement === document.documentElement) { 31 | return null; 32 | } 33 | // Recursively look up to the element tree and find hidden ancestor. 34 | return findHiddenInTree(element.parentElement); 35 | } 36 | 37 | // Force passed element to be visible. 38 | // (for a short while to be able to measure its actual dimension) 39 | export function showInstantly(element) { 40 | // Cache initial style 41 | element.initialDisplay = element.style.display; 42 | element.initialWidth = element.style.width; 43 | element.initialHeight = element.style.height; 44 | element.initialVisibility = element.style.visibility; 45 | // Show it 46 | element.style.display = 'block'; 47 | element.style.width = 'auto'; 48 | element.style.height = 'auto'; 49 | element.style.visibility = 'hidden'; 50 | } 51 | 52 | // Hide element back that has been revealed with `showInstantly` above. 53 | export function hideBack(element) { 54 | element.style.display = element.initialDisplay; 55 | element.style.width = element.initialWidth; 56 | element.style.height = element.initialHeight; 57 | element.style.visibility = element.initialVisibility; 58 | } 59 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | (function ($) { 2 | var presetList = { 3 | Starting_at_2nd: { 4 | startIndex: 1 5 | }, 6 | Starting_at_last: { 7 | startIndex: -1 8 | }, 9 | Disabled_3rd: { 10 | disabledIndex: 2 11 | }, 12 | Disabled_last_2: { 13 | disabledIndex: [-1, -2] 14 | }, 15 | Disabled_last_2_starting_at_1: { 16 | startIndex: 1, 17 | disabledIndex: [-1, -2] 18 | }, 19 | Equal_heights: { 20 | panelHeight: 'equal' 21 | }, 22 | Adaptive_heights: { 23 | panelHeight: 'adaptive' 24 | }, 25 | On_hover: { 26 | selectEvent: 'hover' 27 | }, 28 | Autoplay: { 29 | autoplay: true 30 | }, 31 | Autoplay_every_1s: { 32 | autoplay: true, 33 | autoplayInterval: 1000 34 | }, 35 | Non_responsive: { 36 | breakpoint: 0 37 | }, 38 | Destroyed_responsive: { 39 | breakpointLayout: 'destroy' 40 | }, 41 | Accordion_under_1000px: { 42 | breakpoint: 1000 43 | }, 44 | Accordion_slide_effect: { 45 | slidingAccordion: true 46 | }, 47 | Transition_2s: { 48 | transitionDuration: 2000 49 | }, 50 | Debounce_500ms: { 51 | resizeTimeout: 500 52 | }, 53 | Manual_keyboard: { 54 | keyboard: 'focus' 55 | }, 56 | No_keyboard: { 57 | keyboard: false 58 | }, 59 | No_hash_change: { 60 | history: false 61 | }, 62 | PushState: { 63 | history: 'push' 64 | } 65 | }; 66 | 67 | var $outer = $('#outer'); 68 | var $preset = $('#preset'); 69 | var $dump = $('#dump'); 70 | 71 | // insert preset data into 201 | 202 | 203 | 204 |
205 |

206 |       
207 |
208 | 209 |
210 |
211 | 212 |
213 |
214 | 215 |
216 |
217 | 218 |
219 | 220 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | -------------------------------------------------------------------------------- /docs/assets/style.css: -------------------------------------------------------------------------------- 1 | /* Variables (just reference) */ 2 | :root { 3 | --responsive-breakpoint: 800px; 4 | --color-accent: #2196f3; 5 | --color-accent-dark: #1769aa; 6 | --color-accent-light: #4dabf5; 7 | --color-text: #333; 8 | --color-fade: #f4f6f9; 9 | } 10 | 11 | /* Normalize (extracted) */ 12 | html { 13 | line-height: 1.15; 14 | -ms-text-size-adjust: 100%; 15 | -webkit-text-size-adjust: 100%; 16 | } 17 | body { 18 | margin: 0; 19 | } 20 | article, 21 | aside, 22 | figcaption, 23 | figure, 24 | footer, 25 | header, 26 | main, 27 | nav, 28 | section { 29 | display: block; 30 | } 31 | a { 32 | background-color: transparent; 33 | -webkit-text-decoration-skip: objects; 34 | } 35 | b, 36 | strong { 37 | font-weight: 700; 38 | } 39 | code, 40 | kbd, 41 | samp, 42 | pre { 43 | letter-spacing: 0; 44 | word-spacing: 0; 45 | font-family: monospace, monospace; 46 | font-size: 1em; 47 | white-space: pre; 48 | } 49 | sub, 50 | sup { 51 | position: relative; 52 | line-height: 0; 53 | vertical-align: baseline; 54 | font-size: 75%; 55 | } 56 | sub { 57 | bottom: -0.25em; 58 | } 59 | sup { 60 | top: -0.5em; 61 | } 62 | img { 63 | border-style: none; 64 | } 65 | svg:not(:root) { 66 | overflow: hidden; 67 | } 68 | button, 69 | input, 70 | optgroup, 71 | select, 72 | textarea { 73 | margin: 0; 74 | line-height: 1.15; 75 | font-family: inherit; 76 | font-size: 100%; 77 | } 78 | button, 79 | input { 80 | overflow: visible; 81 | } 82 | button, 83 | select { 84 | text-transform: none; 85 | } 86 | [type='search']::-webkit-search-cancel-button, 87 | [type='search']::-webkit-search-decoration { 88 | -webkit-appearance: none; 89 | } 90 | [hidden] { 91 | display: none; 92 | } 93 | 94 | /* Elements and pseudos */ 95 | html, 96 | body { 97 | height: 100%; 98 | } 99 | html { 100 | font-size: 15px; 101 | } 102 | body { 103 | /* @base-style */ 104 | position: relative; 105 | overflow: hidden; 106 | color: #333; 107 | line-height: 1.7em; 108 | letter-spacing: -0.015em; 109 | word-spacing: 0.025em; 110 | font-family: 'Noto Sans KR', -apple-system, BlinkMacSystemFont, system-ui, 111 | sans-serif; 112 | font-size: 1rem; 113 | word-break: break-all; 114 | word-break: keep-all; 115 | hyphens: auto; 116 | -webkit-font-smoothing: antialiased; 117 | -moz-osx-font-smoothing: grayscale; 118 | } 119 | body:lang(en) { 120 | letter-spacing: 0; 121 | font-family: 'Open Sans', -apple-system, BlinkMacSystemFont, system-ui, 122 | sans-serif; 123 | } 124 | @media (min-width: 800px) { 125 | html { 126 | font-size: medium; 127 | } 128 | body { 129 | line-height: 1.875em; 130 | } 131 | } 132 | a { 133 | color: #2196f3; 134 | text-decoration: none; 135 | } 136 | button, 137 | select { 138 | user-select: none; 139 | } 140 | code, 141 | kbd, 142 | samp, 143 | pre { 144 | font-family: 'Roboto Mono', monospace; 145 | } 146 | code, 147 | samp { 148 | box-decoration-break: clone; 149 | } 150 | 151 | /* Modules */ 152 | .button { 153 | position: relative; 154 | display: inline-block; 155 | margin: 0; 156 | padding: 0; 157 | box-sizing: border-box; 158 | color: inherit; 159 | background: transparent; 160 | border: 0; 161 | vertical-align: middle; 162 | appearance: none; 163 | cursor: pointer; 164 | -webkit-tap-highlight-color: transparent; 165 | } 166 | 167 | .button-primary { 168 | margin: 0.2em 0; 169 | min-width: 12em; 170 | padding: 1em 1.5em; 171 | color: rgba(255, 255, 255, 0.92); 172 | background: #2196f3; 173 | border-radius: 4px; 174 | font-weight: 700; 175 | text-align: center; 176 | transition: background-color 0.1s; 177 | } 178 | .button-primary:focus, 179 | .button-primary:hover { 180 | background-color: #1769aa; 181 | } 182 | .button-primary:focus { 183 | box-shadow: 0 0 0 4px #99c7f6; 184 | } 185 | 186 | .dropdown { 187 | position: absolute; 188 | top: 0; 189 | left: 0; 190 | margin: 0; 191 | overflow: hidden; 192 | padding: 10px 0; 193 | visibility: hidden; 194 | color: #333; 195 | background-color: #fff; 196 | border-radius: 4px; 197 | box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23); 198 | outline: 0; 199 | line-height: 1.7em; 200 | font-size: 1.1rem; 201 | list-style: none; 202 | user-select: none; 203 | opacity: 0; 204 | transform: scale(0.7); 205 | transform-origin: 0 0; 206 | transition: opacity 200ms cubic-bezier(0.4, 0, 0.2, 1) 0ms, 207 | visibility 0s linear 200ms, transform 200ms cubic-bezier(0.4, 0, 0.2, 1) 0ms; 208 | } 209 | .dropdown-expanded { 210 | visibility: visible; 211 | opacity: 1; 212 | transform: scale(1); 213 | transition: opacity 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms, 214 | visibility 0s linear 0s, transform 200ms cubic-bezier(0.4, 0, 0.2, 1) 0ms; 215 | } 216 | .dropdown-link { 217 | display: block; 218 | min-width: 6em; 219 | padding: 0.75em 10px; 220 | color: inherit; 221 | cursor: pointer; 222 | -webkit-tab-highlight-color: transparent; 223 | } 224 | .dropdown-link:hover { 225 | background-color: #f4f6f9; 226 | } 227 | .dropdown-link:focus, 228 | .dropdown-link[aria-selected='true'] { 229 | /* color: #fff; */ 230 | /* background-color: #2196f3; */ 231 | background-color: #f0f0f2; 232 | outline: 0; 233 | } 234 | 235 | @media (min-width: 800px) { 236 | .dropdown { 237 | padding: 0; 238 | font-size: 1rem; 239 | } 240 | } 241 | 242 | .mui-icon { 243 | display: inline-block; 244 | fill: currentColor; 245 | width: 1em; 246 | height: 1em; 247 | font-size: 1.5rem; 248 | user-select: none; 249 | } 250 | 251 | .mui-button { 252 | min-width: 5em; 253 | padding: 0 0.8em; 254 | border-radius: 4px; 255 | line-height: 1.7em; 256 | font-size: 1rem; 257 | font-weight: 500; 258 | transition: background-color 150ms cubic-bezier(0.4, 0, 0.2, 1) 0ms; 259 | } 260 | .mui-icon-xs { 261 | width: 16px; 262 | height: 16px; 263 | } 264 | .mui-icon-sm { 265 | width: 24px; 266 | height: 24px; 267 | } 268 | .mui-icon-md { 269 | width: 32px; 270 | height: 32px; 271 | } 272 | .mui-icon-lg { 273 | width: 48px; 274 | height: 48px; 275 | } 276 | .mui-button:focus { 277 | outline: 0; 278 | } 279 | .mui-button:hover { 280 | background-color: rgba(0, 0, 0, 0.08); 281 | } 282 | .mui-button .mui-icon, 283 | .mui-button-text { 284 | display: inline-block; 285 | vertical-align: middle; 286 | } 287 | .mui-button-text { 288 | font-size: 0.95rem; 289 | font-weight: 500; 290 | } 291 | .mui-icon-button { 292 | position: relative; 293 | min-width: auto; 294 | overflow: visible; 295 | border-radius: 50%; 296 | } 297 | .mui-icon-button .mui-icon { 298 | position: absolute; 299 | top: 0; 300 | right: 0; 301 | bottom: 0; 302 | left: 0; 303 | margin: auto; 304 | } 305 | 306 | .tip { 307 | position: relative; 308 | padding: 0.75em 1.5em; 309 | margin: 1em 0; 310 | color: #707037; 311 | background: #fff; 312 | border: 4px solid #bba; 313 | border-radius: 4px; 314 | } 315 | 316 | /* Layout: header */ 317 | .header { 318 | position: relative; 319 | height: 64px; 320 | z-index: 11; 321 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.12); 322 | } 323 | .header::after { 324 | content: ''; 325 | display: table; 326 | clear: both; 327 | } 328 | .header-pad { 329 | padding: 0 20px; 330 | } 331 | .header-title { 332 | margin: 0; 333 | display: inline-block; 334 | line-height: 64px; 335 | font-size: 24px; 336 | font-weight: 400; 337 | } 338 | .header-menu { 339 | float: right; 340 | height: 48px; 341 | margin: 0; 342 | padding: 8px 0; 343 | list-style: none; 344 | } 345 | .header-menu-item { 346 | position: relative; 347 | float: left; 348 | } 349 | .header-menu .mui-button { 350 | float: left; 351 | height: 48px; 352 | } 353 | .header-menu .mui-icon-button { 354 | width: 48px; 355 | } 356 | .select-language { 357 | width: 5.5em; 358 | padding: 0 8px; 359 | font-size: 0.9rem; 360 | text-align: center; 361 | background: #fff; 362 | border: 1px solid #ddd; 363 | appearance: none; 364 | } 365 | 366 | /* Layout: sidebar */ 367 | .sidebar { 368 | position: fixed; 369 | top: 64px; 370 | bottom: 0; 371 | left: 0; 372 | width: 240px; 373 | display: none; 374 | overflow-y: scroll; 375 | z-index: 10; 376 | /* transform: translate3d(-100%, 0, 0); */ 377 | /* transition: transform 0.4s cubic-bezier(0.25, 0.8, 0.25, 1); */ 378 | background-color: #fff; 379 | -webkit-overflow-scrolling: touch; 380 | -ms-overflow-style: -ms-autohiding-scrollbar; 381 | } 382 | .sidebar-pad { 383 | padding: 40px 0 50px 20px; 384 | } 385 | 386 | @media (min-width: 800px) { 387 | .sidebar { 388 | position: absolute; 389 | display: block; 390 | /* transform: none; */ 391 | } 392 | } 393 | 394 | /* Layout: nav */ 395 | .nav { 396 | position: relative; 397 | padding-left: 20px; 398 | } 399 | .nav-group { 400 | margin: 0; 401 | padding-left: 0; 402 | list-style: none; 403 | } 404 | .nav-group .nav-group { 405 | padding-left: 2em; 406 | } 407 | .nav-item { 408 | margin-bottom: 0.5em; 409 | } 410 | .nav-link { 411 | display: block; 412 | padding: 0.5em 0; 413 | color: inherit; 414 | line-height: 1.15; 415 | } 416 | .nav-link:focus { 417 | outline: 0; 418 | } 419 | .nav-link .hover-underline { 420 | position: relative; 421 | padding: 0 0.25em; 422 | } 423 | .nav-link .hover-underline::after { 424 | content: ''; 425 | position: absolute; 426 | top: 50%; 427 | left: 0; 428 | width: 100%; 429 | max-width: 0; 430 | height: 0.75em; 431 | z-index: -1; 432 | opacity: 0.5; 433 | background-color: #99c7f6; 434 | transform: skew(-15deg); 435 | transition: max-width 0.3s ease; 436 | } 437 | .nav-link:hover .hover-underline::after, 438 | .nav-link.skltbs-active .hover-underline::after { 439 | max-width: 100%; 440 | } 441 | .nav-link.skltbs-active { 442 | color: #000; 443 | font-weight: 700; 444 | } 445 | 446 | /* Layout: content */ 447 | .content { 448 | position: absolute; 449 | top: 64px; 450 | right: 0; 451 | bottom: 0; 452 | left: 0; 453 | overflow-y: scroll; 454 | -webkit-overflow-scrolling: touch; 455 | -ms-overflow-style: -ms-autohiding-scrollbar; 456 | } 457 | .content-pad { 458 | padding: 40px 20px 50px 30px; 459 | } 460 | .content-holder { 461 | position: relative; 462 | max-width: 1000px; 463 | min-width: 1px; 464 | margin: 0 auto; 465 | } 466 | .section { 467 | margin-bottom: 4rem; 468 | outline: 0; 469 | } 470 | .section::before, 471 | .section::after { 472 | content: ''; 473 | display: table; 474 | clear: both; 475 | } 476 | 477 | @media (min-width: 800px) { 478 | .content { 479 | left: 240px; 480 | } 481 | .page-init .section { 482 | position: relative; 483 | transition: opacity 0.3s; 484 | } 485 | .page-init .section.skltbs-leave { 486 | position: absolute; 487 | top: 0; 488 | display: block !important; 489 | opacity: 1; 490 | } 491 | .page-init .section.skltbs-leave-active { 492 | opacity: 0; 493 | } 494 | .page-init .section.skltbs-enter { 495 | opacity: 0; 496 | } 497 | .page-init .section.skltbs-enter-active { 498 | opacity: 1; 499 | } 500 | } 501 | 502 | /* Body content */ 503 | .content h2, 504 | .content h3, 505 | .content h4, 506 | .content h5, 507 | .content h6 { 508 | margin: 4rem 0 1.25em; 509 | line-height: 1.5em; 510 | font-size: 1rem; 511 | font-weight: 400; 512 | } 513 | .content h2 { 514 | margin-top: 0; 515 | font-size: 2rem; 516 | } 517 | /* pull scroll down when jumping to hash (header) */ 518 | .content h2::before { 519 | content: ''; 520 | display: block; 521 | margin-top: -30px; 522 | height: 30px; 523 | visibility: hidden; 524 | } 525 | .content h3 { 526 | position: relative; 527 | font-size: 1.4rem; 528 | } 529 | .content h3::before { 530 | content: '#'; 531 | position: absolute; 532 | left: -1em; 533 | color: #ccc; 534 | } 535 | 536 | @media (min-width: 800px) { 537 | .content h2 { 538 | font-size: 1.75rem; 539 | } 540 | } 541 | 542 | .content a { 543 | border-bottom: 1px solid #d1eef9; 544 | } 545 | .content a:focus, 546 | .content a:hover { 547 | border-bottom-color: currentColor; 548 | } 549 | 550 | .content p, 551 | .content dl, 552 | .content ol, 553 | .content ul:not([class]) { 554 | margin: 1.5rem 0; 555 | } 556 | .content figure { 557 | margin: 2rem 0; 558 | } 559 | .content ul:not([class]) { 560 | padding-left: 1rem; 561 | } 562 | .content ul:not([class]) li { 563 | position: relative; 564 | margin: 0 0 0.75em; 565 | list-style: none; 566 | } 567 | .content ul:not([class]) li::before { 568 | content: ''; 569 | position: absolute; 570 | top: 0.94rem; 571 | left: -1rem; 572 | display: inline-block; 573 | width: 4px; 574 | height: 4px; 575 | margin-top: -2px; 576 | background-color: #99c7f6; 577 | border-radius: 50%; 578 | } 579 | 580 | .content table { 581 | width: 100%; 582 | border-collapse: collapse; 583 | border-bottom: 1px solid #d4dbdd; 584 | line-height: 1.6em; 585 | /* font-size: 0.95rem; */ 586 | } 587 | .content table td, 588 | .content table th { 589 | padding: 1em 0.5em; 590 | vertical-align: top; 591 | border-top: 1px solid #d4dbdd; 592 | text-align: left; 593 | } 594 | .content table th { 595 | font-weight: 700; 596 | } 597 | .content table thead th { 598 | padding: 1em; 599 | color: #5c6c70; 600 | background-color: #f4f6f9; 601 | } 602 | .content table td > *:first-child { 603 | margin-top: 0; 604 | } 605 | .content table td > *:last-child, 606 | .content table td li:last-child { 607 | margin-bottom: 0; 608 | } 609 | 610 | .table-scroller { 611 | display: block; 612 | min-height: 1px; 613 | margin: 1.5rem 0; 614 | overflow-x: auto; 615 | -webkit-overflow-scrolling: touch; 616 | -ms-overflow-style: -ms-autohiding-scrollbar; 617 | } 618 | .table-scroller > table { 619 | min-width: 900px; 620 | } 621 | 622 | .content code, 623 | .content samp { 624 | background-color: #f4f6f9; 625 | font-size: 0.92em; 626 | } 627 | .content code:not([class]), 628 | .content samp { 629 | color: #005cc5; 630 | padding: 0 0.2em; 631 | } 632 | .content kbd { 633 | /* https://auth0.github.io/kbd/ */ 634 | display: inline-block; 635 | padding: 0 0.3em 0 0.4em; 636 | background-color: #eee; 637 | border-radius: 3px; 638 | box-shadow: 1px 1px 1px #777; 639 | letter-spacing: 0; 640 | line-height: 1.6em; 641 | font-size: 0.85rem; 642 | user-select: none; 643 | } 644 | 645 | /* Content: option definition */ 646 | .option-definition { 647 | } 648 | .option-name { 649 | font-weight: 700; 650 | } 651 | .option-type, 652 | .option-default, 653 | .option-description { 654 | margin-left: 0; 655 | } 656 | .option-name, 657 | .option-type, 658 | .option-default { 659 | display: inline-block; 660 | } 661 | .option-type em, 662 | .option-default em { 663 | font-style: normal; 664 | } 665 | .option-description { 666 | margin-top: 0.75em; 667 | } 668 | 669 | /* Content: object params style */ 670 | .object-detail { 671 | position: relative; 672 | margin-top: 0.75em; 673 | padding-left: 2.75em; 674 | } 675 | .object-detail::before { 676 | content: ''; 677 | position: absolute; 678 | top: 0; 679 | bottom: 0.94rem; 680 | left: 0; 681 | margin-bottom: -2px; 682 | border-left: 2px dashed #d4dbdd; 683 | } 684 | 685 | /* Content: FAQ */ 686 | .faq-tab { 687 | position: relative; 688 | display: block; 689 | width: 100%; 690 | padding: 0.75em 20px; 691 | background: transparent; 692 | border: 0; 693 | border-bottom: 1px solid #aaa; 694 | line-height: 1.15; 695 | font-family: inherit; 696 | font-size: 1.4rem; 697 | text-align: left; 698 | appearance: none; 699 | user-select: none; 700 | touch-action: manipulation; 701 | } 702 | .faq-tab:disabled { 703 | cursor: not-allowed; 704 | touch-action: none; 705 | } 706 | .faq-tab:focus { 707 | outline: 0; 708 | border-color: #0a69ca; 709 | box-shadow: inset 0 0 0 2px #0a69ca; 710 | } 711 | .faq-tab.faq-active { 712 | color: white; 713 | background-color: #2b8ff5; 714 | border-color: #2b8ff5; 715 | } 716 | .faq-tab .mui-icon { 717 | position: absolute; 718 | top: 0; 719 | right: 20px; 720 | bottom: 0; 721 | margin: auto; 722 | transition: transform 0s; 723 | } 724 | .faq-tab:disabled .mui-icon { 725 | color: #bbb; 726 | } 727 | .faq-tab.faq-active .mui-icon { 728 | transform: rotate(-0.5turn); 729 | transition: transform 0.3s; 730 | } 731 | .faq-panel { 732 | position: relative; 733 | padding: 20px; 734 | z-index: 0; 735 | border: 1px solid #ccc; 736 | } 737 | .faq-panel:focus { 738 | outline: 0; 739 | border-color: #0a69ca; 740 | box-shadow: inset 0 0 0 1px #0a69ca; 741 | } 742 | .faq-panel-heading { 743 | margin: 2rem 0 0 !important; 744 | } 745 | .faq-panel-heading:first-child { 746 | margin-top: 0 !important; 747 | } 748 | .faq-panel p { 749 | margin: 0.75em 0; 750 | } 751 | 752 | /* Helpers */ 753 | .nonvisual { 754 | position: absolute !important; 755 | width: 1px !important; 756 | height: 1px !important; 757 | margin: -1px !important; 758 | padding: 0 !important; 759 | overflow: hidden !important; 760 | clip: rect(0, 0, 0, 0) !important; 761 | } 762 | 763 | /* Vendors: highlight.js, theme github-gist - modified */ 764 | .hljs { 765 | display: block; 766 | overflow-x: auto; 767 | padding: 1em 1.25em; 768 | color: #333; 769 | background: #f4f6f9; 770 | border-radius: 4px; 771 | font-size: 0.85rem; 772 | -webkit-overflow-scrolling: touch; 773 | -ms-overflow-style: -ms-autohiding-scrollbar; 774 | } 775 | .hljs-comment, 776 | .hljs-meta { 777 | color: #969896; 778 | } 779 | .hljs-variable, 780 | .hljs-template-variable, 781 | .hljs-strong, 782 | .hljs-emphasis, 783 | .hljs-quote { 784 | color: #df5000; 785 | } 786 | .hljs-keyword, 787 | .hljs-selector-tag, 788 | .hljs-type { 789 | color: #d73a49; 790 | } 791 | .hljs-title, 792 | .hljs-attr, 793 | .hljs-symbol, 794 | .hljs-bullet, 795 | .hljs-attribute { 796 | color: #0086b3; 797 | } 798 | .hljs-literal { 799 | color: #005cc5; 800 | } 801 | .hljs-section, 802 | .hljs-name { 803 | /* color: #63a35c; */ 804 | color: #6f42c1; 805 | } 806 | .hljs-tag { 807 | color: #333; 808 | } 809 | .hljs-selector-id, 810 | .hljs-selector-class, 811 | .hljs-selector-attr, 812 | .hljs-selector-pseudo { 813 | color: #6f42c1; 814 | } 815 | .hljs-addition { 816 | color: #55a532; 817 | background-color: #eaffea; 818 | } 819 | .hljs-deletion { 820 | color: #bd2c00; 821 | background-color: #833838; 822 | } 823 | .hljs-link { 824 | text-decoration: underline; 825 | } 826 | .hljs-number { 827 | /* color: #005cc5; */ 828 | color: #6f42c1; 829 | } 830 | .hljs-string { 831 | /* color: #032f62; */ 832 | color: #1ca77e; 833 | } 834 | -------------------------------------------------------------------------------- /docs/assets/highlight/highlight.pack.js: -------------------------------------------------------------------------------- 1 | /* 2 | Highlight.js 10.1.2 (edd73d24) 3 | License: BSD-3-Clause 4 | Copyright (c) 2006-2020, Ivan Sagalaev 5 | */ 6 | var hljs=function(){"use strict";function e(n){Object.freeze(n);var t="function"==typeof n;return Object.getOwnPropertyNames(n).forEach((function(r){!Object.hasOwnProperty.call(n,r)||null===n[r]||"object"!=typeof n[r]&&"function"!=typeof n[r]||t&&("caller"===r||"callee"===r||"arguments"===r)||Object.isFrozen(n[r])||e(n[r])})),n}class n{constructor(e){void 0===e.data&&(e.data={}),this.data=e.data}ignoreMatch(){this.ignore=!0}}function t(e){return e.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function r(e,...n){var t={};for(const n in e)t[n]=e[n];return n.forEach((function(e){for(const n in e)t[n]=e[n]})),t}function a(e){return e.nodeName.toLowerCase()}var i=Object.freeze({__proto__:null,escapeHTML:t,inherit:r,nodeStream:function(e){var n=[];return function e(t,r){for(var i=t.firstChild;i;i=i.nextSibling)3===i.nodeType?r+=i.nodeValue.length:1===i.nodeType&&(n.push({event:"start",offset:r,node:i}),r=e(i,r),a(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:r,node:i}));return r}(e,0),n},mergeStreams:function(e,n,r){var i=0,s="",o=[];function l(){return e.length&&n.length?e[0].offset!==n[0].offset?e[0].offset"}function u(e){s+=""}function d(e){("start"===e.event?c:u)(e.node)}for(;e.length||n.length;){var g=l();if(s+=t(r.substring(i,g[0].offset)),i=g[0].offset,g===e){o.reverse().forEach(u);do{d(g.splice(0,1)[0]),g=l()}while(g===e&&g.length&&g[0].offset===i);o.reverse().forEach(c)}else"start"===g[0].event?o.push(g[0].node):o.pop(),d(g.splice(0,1)[0])}return s+t(r.substr(i))}});const s="",o=e=>!!e.kind;class l{constructor(e,n){this.buffer="",this.classPrefix=n.classPrefix,e.walk(this)}addText(e){this.buffer+=t(e)}openNode(e){if(!o(e))return;let n=e.kind;e.sublanguage||(n=`${this.classPrefix}${n}`),this.span(n)}closeNode(e){o(e)&&(this.buffer+=s)}value(){return this.buffer}span(e){this.buffer+=``}}class c{constructor(){this.rootNode={children:[]},this.stack=[this.rootNode]}get top(){return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){this.top.children.push(e)}openNode(e){const n={kind:e,children:[]};this.add(n),this.stack.push(n)}closeNode(){if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)}walk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,n){return"string"==typeof n?e.addText(n):n.children&&(e.openNode(n),n.children.forEach(n=>this._walk(e,n)),e.closeNode(n)),e}static _collapse(e){"string"!=typeof e&&e.children&&(e.children.every(e=>"string"==typeof e)?e.children=[e.children.join("")]:e.children.forEach(e=>{c._collapse(e)}))}}class u extends c{constructor(e){super(),this.options=e}addKeyword(e,n){""!==e&&(this.openNode(n),this.addText(e),this.closeNode())}addText(e){""!==e&&this.add(e)}addSublanguage(e,n){const t=e.root;t.kind=n,t.sublanguage=!0,this.add(t)}toHTML(){return new l(this,this.options).value()}finalize(){return!0}}function d(e){return e?"string"==typeof e?e:e.source:null}const g="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",h={begin:"\\\\[\\s\\S]",relevance:0},f={className:"string",begin:"'",end:"'",illegal:"\\n",contains:[h]},p={className:"string",begin:'"',end:'"',illegal:"\\n",contains:[h]},b={begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},m=function(e,n,t={}){var a=r({className:"comment",begin:e,end:n,contains:[]},t);return a.contains.push(b),a.contains.push({className:"doctag",begin:"(?:TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):",relevance:0}),a},v=m("//","$"),x=m("/\\*","\\*/"),E=m("#","$");var _=Object.freeze({__proto__:null,IDENT_RE:"[a-zA-Z]\\w*",UNDERSCORE_IDENT_RE:"[a-zA-Z_]\\w*",NUMBER_RE:"\\b\\d+(\\.\\d+)?",C_NUMBER_RE:g,BINARY_NUMBER_RE:"\\b(0b[01]+)",RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",SHEBANG:(e={})=>{const n=/^#![ ]*\//;return e.binary&&(e.begin=function(...e){return e.map(e=>d(e)).join("")}(n,/.*\b/,e.binary,/\b.*/)),r({className:"meta",begin:n,end:/$/,relevance:0,"on:begin":(e,n)=>{0!==e.index&&n.ignoreMatch()}},e)},BACKSLASH_ESCAPE:h,APOS_STRING_MODE:f,QUOTE_STRING_MODE:p,PHRASAL_WORDS_MODE:b,COMMENT:m,C_LINE_COMMENT_MODE:v,C_BLOCK_COMMENT_MODE:x,HASH_COMMENT_MODE:E,NUMBER_MODE:{className:"number",begin:"\\b\\d+(\\.\\d+)?",relevance:0},C_NUMBER_MODE:{className:"number",begin:g,relevance:0},BINARY_NUMBER_MODE:{className:"number",begin:"\\b(0b[01]+)",relevance:0},CSS_NUMBER_MODE:{className:"number",begin:"\\b\\d+(\\.\\d+)?(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},REGEXP_MODE:{begin:/(?=\/[^/\n]*\/)/,contains:[{className:"regexp",begin:/\//,end:/\/[gimuy]*/,illegal:/\n/,contains:[h,{begin:/\[/,end:/\]/,relevance:0,contains:[h]}]}]},TITLE_MODE:{className:"title",begin:"[a-zA-Z]\\w*",relevance:0},UNDERSCORE_TITLE_MODE:{className:"title",begin:"[a-zA-Z_]\\w*",relevance:0},METHOD_GUARD:{begin:"\\.\\s*[a-zA-Z_]\\w*",relevance:0},END_SAME_AS_BEGIN:function(e){return Object.assign(e,{"on:begin":(e,n)=>{n.data._beginMatch=e[1]},"on:end":(e,n)=>{n.data._beginMatch!==e[1]&&n.ignoreMatch()}})}}),N="of and for in not or if then".split(" ");function w(e,n){return n?+n:function(e){return N.includes(e.toLowerCase())}(e)?0:1}const R=t,y=r,{nodeStream:O,mergeStreams:k}=i,M=Symbol("nomatch");return function(t){var a=[],i=Object.create(null),s=Object.create(null),o=[],l=!0,c=/(^(<[^>]+>|\t|)+|\n)/gm,g="Could not find the language '{}', did you forget to load/include a language module?";const h={disableAutodetect:!0,name:"Plain text",contains:[]};var f={noHighlightRe:/^(no-?highlight)$/i,languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:null,__emitter:u};function p(e){return f.noHighlightRe.test(e)}function b(e,n,t,r){var a={code:n,language:e};S("before:highlight",a);var i=a.result?a.result:m(a.language,a.code,t,r);return i.code=a.code,S("after:highlight",i),i}function m(e,t,a,s){var o=t;function c(e,n){var t=E.case_insensitive?n[0].toLowerCase():n[0];return Object.prototype.hasOwnProperty.call(e.keywords,t)&&e.keywords[t]}function u(){null!=y.subLanguage?function(){if(""!==A){var e=null;if("string"==typeof y.subLanguage){if(!i[y.subLanguage])return void k.addText(A);e=m(y.subLanguage,A,!0,O[y.subLanguage]),O[y.subLanguage]=e.top}else e=v(A,y.subLanguage.length?y.subLanguage:null);y.relevance>0&&(I+=e.relevance),k.addSublanguage(e.emitter,e.language)}}():function(){if(!y.keywords)return void k.addText(A);let e=0;y.keywordPatternRe.lastIndex=0;let n=y.keywordPatternRe.exec(A),t="";for(;n;){t+=A.substring(e,n.index);const r=c(y,n);if(r){const[e,a]=r;k.addText(t),t="",I+=a,k.addKeyword(n[0],e)}else t+=n[0];e=y.keywordPatternRe.lastIndex,n=y.keywordPatternRe.exec(A)}t+=A.substr(e),k.addText(t)}(),A=""}function h(e){return e.className&&k.openNode(e.className),y=Object.create(e,{parent:{value:y}})}function p(e){return 0===y.matcher.regexIndex?(A+=e[0],1):(L=!0,0)}var b={};function x(t,r){var i=r&&r[0];if(A+=t,null==i)return u(),0;if("begin"===b.type&&"end"===r.type&&b.index===r.index&&""===i){if(A+=o.slice(r.index,r.index+1),!l){const n=Error("0 width match regex");throw n.languageName=e,n.badRule=b.rule,n}return 1}if(b=r,"begin"===r.type)return function(e){var t=e[0],r=e.rule;const a=new n(r),i=[r.__beforeBegin,r["on:begin"]];for(const n of i)if(n&&(n(e,a),a.ignore))return p(t);return r&&r.endSameAsBegin&&(r.endRe=RegExp(t.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&"),"m")),r.skip?A+=t:(r.excludeBegin&&(A+=t),u(),r.returnBegin||r.excludeBegin||(A=t)),h(r),r.returnBegin?0:t.length}(r);if("illegal"===r.type&&!a){const e=Error('Illegal lexeme "'+i+'" for mode "'+(y.className||"")+'"');throw e.mode=y,e}if("end"===r.type){var s=function(e){var t=e[0],r=o.substr(e.index),a=function e(t,r,a){let i=function(e,n){var t=e&&e.exec(n);return t&&0===t.index}(t.endRe,a);if(i){if(t["on:end"]){const e=new n(t);t["on:end"](r,e),e.ignore&&(i=!1)}if(i){for(;t.endsParent&&t.parent;)t=t.parent;return t}}if(t.endsWithParent)return e(t.parent,r,a)}(y,e,r);if(!a)return M;var i=y;i.skip?A+=t:(i.returnEnd||i.excludeEnd||(A+=t),u(),i.excludeEnd&&(A=t));do{y.className&&k.closeNode(),y.skip||y.subLanguage||(I+=y.relevance),y=y.parent}while(y!==a.parent);return a.starts&&(a.endSameAsBegin&&(a.starts.endRe=a.endRe),h(a.starts)),i.returnEnd?0:t.length}(r);if(s!==M)return s}if("illegal"===r.type&&""===i)return 1;if(B>1e5&&B>3*r.index)throw Error("potential infinite loop, way more iterations than matches");return A+=i,i.length}var E=T(e);if(!E)throw console.error(g.replace("{}",e)),Error('Unknown language: "'+e+'"');var _=function(e){function n(n,t){return RegExp(d(n),"m"+(e.case_insensitive?"i":"")+(t?"g":""))}class t{constructor(){this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0}addRule(e,n){n.position=this.position++,this.matchIndexes[this.matchAt]=n,this.regexes.push([n,e]),this.matchAt+=function(e){return RegExp(e.toString()+"|").exec("").length-1}(e)+1}compile(){0===this.regexes.length&&(this.exec=()=>null);const e=this.regexes.map(e=>e[1]);this.matcherRe=n(function(e,n="|"){for(var t=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./,r=0,a="",i=0;i0&&(a+=n),a+="(";o.length>0;){var l=t.exec(o);if(null==l){a+=o;break}a+=o.substring(0,l.index),o=o.substring(l.index+l[0].length),"\\"===l[0][0]&&l[1]?a+="\\"+(+l[1]+s):(a+=l[0],"("===l[0]&&r++)}a+=")"}return a}(e),!0),this.lastIndex=0}exec(e){this.matcherRe.lastIndex=this.lastIndex;const n=this.matcherRe.exec(e);if(!n)return null;const t=n.findIndex((e,n)=>n>0&&void 0!==e),r=this.matchIndexes[t];return n.splice(0,t),Object.assign(n,r)}}class a{constructor(){this.rules=[],this.multiRegexes=[],this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(e){if(this.multiRegexes[e])return this.multiRegexes[e];const n=new t;return this.rules.slice(e).forEach(([e,t])=>n.addRule(e,t)),n.compile(),this.multiRegexes[e]=n,n}considerAll(){this.regexIndex=0}addRule(e,n){this.rules.push([e,n]),"begin"===n.type&&this.count++}exec(e){const n=this.getMatcher(this.regexIndex);n.lastIndex=this.lastIndex;const t=n.exec(e);return t&&(this.regexIndex+=t.position+1,this.regexIndex===this.count&&(this.regexIndex=0)),t}}function i(e,n){const t=e.input[e.index-1],r=e.input[e.index+e[0].length];"."!==t&&"."!==r||n.ignoreMatch()}if(e.contains&&e.contains.includes("self"))throw Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.");return function t(s,o){const l=s;if(s.compiled)return l;s.compiled=!0,s.__beforeBegin=null,s.keywords=s.keywords||s.beginKeywords;let c=null;if("object"==typeof s.keywords&&(c=s.keywords.$pattern,delete s.keywords.$pattern),s.keywords&&(s.keywords=function(e,n){var t={};return"string"==typeof e?r("keyword",e):Object.keys(e).forEach((function(n){r(n,e[n])})),t;function r(e,r){n&&(r=r.toLowerCase()),r.split(" ").forEach((function(n){var r=n.split("|");t[r[0]]=[e,w(r[0],r[1])]}))}}(s.keywords,e.case_insensitive)),s.lexemes&&c)throw Error("ERR: Prefer `keywords.$pattern` to `mode.lexemes`, BOTH are not allowed. (see mode reference) ");return l.keywordPatternRe=n(s.lexemes||c||/\w+/,!0),o&&(s.beginKeywords&&(s.begin="\\b("+s.beginKeywords.split(" ").join("|")+")(?=\\b|\\s)",s.__beforeBegin=i),s.begin||(s.begin=/\B|\b/),l.beginRe=n(s.begin),s.endSameAsBegin&&(s.end=s.begin),s.end||s.endsWithParent||(s.end=/\B|\b/),s.end&&(l.endRe=n(s.end)),l.terminator_end=d(s.end)||"",s.endsWithParent&&o.terminator_end&&(l.terminator_end+=(s.end?"|":"")+o.terminator_end)),s.illegal&&(l.illegalRe=n(s.illegal)),void 0===s.relevance&&(s.relevance=1),s.contains||(s.contains=[]),s.contains=[].concat(...s.contains.map((function(e){return function(e){return e.variants&&!e.cached_variants&&(e.cached_variants=e.variants.map((function(n){return r(e,{variants:null},n)}))),e.cached_variants?e.cached_variants:function e(n){return!!n&&(n.endsWithParent||e(n.starts))}(e)?r(e,{starts:e.starts?r(e.starts):null}):Object.isFrozen(e)?r(e):e}("self"===e?s:e)}))),s.contains.forEach((function(e){t(e,l)})),s.starts&&t(s.starts,o),l.matcher=function(e){const n=new a;return e.contains.forEach(e=>n.addRule(e.begin,{rule:e,type:"begin"})),e.terminator_end&&n.addRule(e.terminator_end,{type:"end"}),e.illegal&&n.addRule(e.illegal,{type:"illegal"}),n}(l),l}(e)}(E),N="",y=s||_,O={},k=new f.__emitter(f);!function(){for(var e=[],n=y;n!==E;n=n.parent)n.className&&e.unshift(n.className);e.forEach(e=>k.openNode(e))}();var A="",I=0,S=0,B=0,L=!1;try{for(y.matcher.considerAll();;){B++,L?L=!1:(y.matcher.lastIndex=S,y.matcher.considerAll());const e=y.matcher.exec(o);if(!e)break;const n=x(o.substring(S,e.index),e);S=e.index+n}return x(o.substr(S)),k.closeAllNodes(),k.finalize(),N=k.toHTML(),{relevance:I,value:N,language:e,illegal:!1,emitter:k,top:y}}catch(n){if(n.message&&n.message.includes("Illegal"))return{illegal:!0,illegalBy:{msg:n.message,context:o.slice(S-100,S+100),mode:n.mode},sofar:N,relevance:0,value:R(o),emitter:k};if(l)return{illegal:!1,relevance:0,value:R(o),emitter:k,language:e,top:y,errorRaised:n};throw n}}function v(e,n){n=n||f.languages||Object.keys(i);var t=function(e){const n={relevance:0,emitter:new f.__emitter(f),value:R(e),illegal:!1,top:h};return n.emitter.addText(e),n}(e),r=t;return n.filter(T).filter(I).forEach((function(n){var a=m(n,e,!1);a.language=n,a.relevance>r.relevance&&(r=a),a.relevance>t.relevance&&(r=t,t=a)})),r.language&&(t.second_best=r),t}function x(e){return f.tabReplace||f.useBR?e.replace(c,e=>"\n"===e?f.useBR?"
":e:f.tabReplace?e.replace(/\t/g,f.tabReplace):e):e}function E(e){let n=null;const t=function(e){var n=e.className+" ";n+=e.parentNode?e.parentNode.className:"";const t=f.languageDetectRe.exec(n);if(t){var r=T(t[1]);return r||(console.warn(g.replace("{}",t[1])),console.warn("Falling back to no-highlight mode for this block.",e)),r?t[1]:"no-highlight"}return n.split(/\s+/).find(e=>p(e)||T(e))}(e);if(p(t))return;S("before:highlightBlock",{block:e,language:t}),f.useBR?(n=document.createElement("div")).innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n"):n=e;const r=n.textContent,a=t?b(t,r,!0):v(r),i=O(n);if(i.length){const e=document.createElement("div");e.innerHTML=a.value,a.value=k(i,O(e),r)}a.value=x(a.value),S("after:highlightBlock",{block:e,result:a}),e.innerHTML=a.value,e.className=function(e,n,t){var r=n?s[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),e.includes(r)||a.push(r),a.join(" ").trim()}(e.className,t,a.language),e.result={language:a.language,re:a.relevance,relavance:a.relevance},a.second_best&&(e.second_best={language:a.second_best.language,re:a.second_best.relevance,relavance:a.second_best.relevance})}const N=()=>{if(!N.called){N.called=!0;var e=document.querySelectorAll("pre code");a.forEach.call(e,E)}};function T(e){return e=(e||"").toLowerCase(),i[e]||i[s[e]]}function A(e,{languageName:n}){"string"==typeof e&&(e=[e]),e.forEach(e=>{s[e]=n})}function I(e){var n=T(e);return n&&!n.disableAutodetect}function S(e,n){var t=e;o.forEach((function(e){e[t]&&e[t](n)}))}Object.assign(t,{highlight:b,highlightAuto:v,fixMarkup:x,highlightBlock:E,configure:function(e){f=y(f,e)},initHighlighting:N,initHighlightingOnLoad:function(){window.addEventListener("DOMContentLoaded",N,!1)},registerLanguage:function(e,n){var r=null;try{r=n(t)}catch(n){if(console.error("Language definition for '{}' could not be registered.".replace("{}",e)),!l)throw n;console.error(n),r=h}r.name||(r.name=e),i[e]=r,r.rawDefinition=n.bind(null,t),r.aliases&&A(r.aliases,{languageName:e})},listLanguages:function(){return Object.keys(i)},getLanguage:T,registerAliases:A,requireLanguage:function(e){var n=T(e);if(n)return n;throw Error("The '{}' language is required, but not loaded.".replace("{}",e))},autoDetection:I,inherit:y,addPlugin:function(e){o.push(e)}}),t.debugMode=function(){l=!1},t.safeMode=function(){l=!0},t.versionString="10.1.2";for(const n in _)"object"==typeof _[n]&&e(_[n]);return Object.assign(t,_),t}({})}();"object"==typeof exports&&"undefined"!=typeof module&&(module.exports=hljs);hljs.registerLanguage("properties",function(){"use strict";return function(e){var n="[ \\t\\f]*",t="("+n+"[:=]"+n+"|[ \\t\\f]+)",a="([^\\\\:= \\t\\f\\n]|\\\\.)+",s={end:t,relevance:0,starts:{className:"string",end:/$/,relevance:0,contains:[{begin:"\\\\\\n"}]}};return{name:".properties",case_insensitive:!0,illegal:/\S/,contains:[e.COMMENT("^\\s*[!#]","$"),{begin:"([^\\\\\\W:= \\t\\f\\n]|\\\\.)+"+t,returnBegin:!0,contains:[{className:"attr",begin:"([^\\\\\\W:= \\t\\f\\n]|\\\\.)+",endsParent:!0,relevance:0}],starts:s},{begin:a+t,returnBegin:!0,relevance:0,contains:[{className:"meta",begin:a,endsParent:!0,relevance:0}],starts:s},{className:"attr",relevance:0,begin:a+n+"$"}]}}}());hljs.registerLanguage("javascript",function(){"use strict";const e=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],n=["true","false","null","undefined","NaN","Infinity"],a=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]);function s(e){return r("(?=",e,")")}function r(...e){return e.map(e=>(function(e){return e?"string"==typeof e?e:e.source:null})(e)).join("")}return function(t){var i="[A-Za-z$_][0-9A-Za-z$_]*",c={begin:/<[A-Za-z0-9\\._:-]+/,end:/\/[A-Za-z0-9\\._:-]+>|\/>/},o={$pattern:"[A-Za-z$_][0-9A-Za-z$_]*",keyword:e.join(" "),literal:n.join(" "),built_in:a.join(" ")},l={className:"number",variants:[{begin:"\\b(0[bB][01]+)n?"},{begin:"\\b(0[oO][0-7]+)n?"},{begin:t.C_NUMBER_RE+"n?"}],relevance:0},E={className:"subst",begin:"\\$\\{",end:"\\}",keywords:o,contains:[]},d={begin:"html`",end:"",starts:{end:"`",returnEnd:!1,contains:[t.BACKSLASH_ESCAPE,E],subLanguage:"xml"}},g={begin:"css`",end:"",starts:{end:"`",returnEnd:!1,contains:[t.BACKSLASH_ESCAPE,E],subLanguage:"css"}},u={className:"string",begin:"`",end:"`",contains:[t.BACKSLASH_ESCAPE,E]};E.contains=[t.APOS_STRING_MODE,t.QUOTE_STRING_MODE,d,g,u,l,t.REGEXP_MODE];var b=E.contains.concat([{begin:/\(/,end:/\)/,contains:["self"].concat(E.contains,[t.C_BLOCK_COMMENT_MODE,t.C_LINE_COMMENT_MODE])},t.C_BLOCK_COMMENT_MODE,t.C_LINE_COMMENT_MODE]),_={className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,contains:b};return{name:"JavaScript",aliases:["js","jsx","mjs","cjs"],keywords:o,contains:[t.SHEBANG({binary:"node",relevance:5}),{className:"meta",relevance:10,begin:/^\s*['"]use (strict|asm)['"]/},t.APOS_STRING_MODE,t.QUOTE_STRING_MODE,d,g,u,t.C_LINE_COMMENT_MODE,t.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{className:"doctag",begin:"@[A-Za-z]+",contains:[{className:"type",begin:"\\{",end:"\\}",relevance:0},{className:"variable",begin:i+"(?=\\s*(-)|$)",endsParent:!0,relevance:0},{begin:/(?=[^\n])\s/,relevance:0}]}]}),t.C_BLOCK_COMMENT_MODE,l,{begin:r(/[{,\n]\s*/,s(r(/(((\/\/.*)|(\/\*(.|\n)*\*\/))\s*)*/,i+"\\s*:"))),relevance:0,contains:[{className:"attr",begin:i+s("\\s*:"),relevance:0}]},{begin:"("+t.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",keywords:"return throw case",contains:[t.C_LINE_COMMENT_MODE,t.C_BLOCK_COMMENT_MODE,t.REGEXP_MODE,{className:"function",begin:"(\\([^(]*(\\([^(]*(\\([^(]*\\))?\\))?\\)|"+t.UNDERSCORE_IDENT_RE+")\\s*=>",returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:t.UNDERSCORE_IDENT_RE},{className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:o,contains:b}]}]},{begin:/,/,relevance:0},{className:"",begin:/\s/,end:/\s*/,skip:!0},{variants:[{begin:"<>",end:""},{begin:c.begin,end:c.end}],subLanguage:"xml",contains:[{begin:c.begin,end:c.end,skip:!0,contains:["self"]}]}],relevance:0},{className:"function",beginKeywords:"function",end:/\{/,excludeEnd:!0,contains:[t.inherit(t.TITLE_MODE,{begin:i}),_],illegal:/\[|%/},{begin:/\$[(.]/},t.METHOD_GUARD,{className:"class",beginKeywords:"class",end:/[{;=]/,excludeEnd:!0,illegal:/[:"\[\]]/,contains:[{beginKeywords:"extends"},t.UNDERSCORE_TITLE_MODE]},{beginKeywords:"constructor",end:/\{/,excludeEnd:!0},{begin:"(get|set)\\s+(?="+i+"\\()",end:/{/,keywords:"get set",contains:[t.inherit(t.TITLE_MODE,{begin:i}),{begin:/\(\)/},_]}],illegal:/#(?!!)/}}}());hljs.registerLanguage("css",function(){"use strict";return function(e){var n={begin:/(?:[A-Z\_\.\-]+|--[a-zA-Z0-9_-]+)\s*:/,returnBegin:!0,end:";",endsWithParent:!0,contains:[{className:"attribute",begin:/\S/,end:":",excludeEnd:!0,starts:{endsWithParent:!0,excludeEnd:!0,contains:[{begin:/[\w-]+\(/,returnBegin:!0,contains:[{className:"built_in",begin:/[\w-]+/},{begin:/\(/,end:/\)/,contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.CSS_NUMBER_MODE]}]},e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,e.C_BLOCK_COMMENT_MODE,{className:"number",begin:"#[0-9A-Fa-f]+"},{className:"meta",begin:"!important"}]}}]};return{name:"CSS",case_insensitive:!0,illegal:/[=\/|'\$]/,contains:[e.C_BLOCK_COMMENT_MODE,{className:"selector-id",begin:/#[A-Za-z0-9_-]+/},{className:"selector-class",begin:/\.[A-Za-z0-9_-]+/},{className:"selector-attr",begin:/\[/,end:/\]/,illegal:"$",contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},{className:"selector-pseudo",begin:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{begin:"@(page|font-face)",lexemes:"@[a-z-]+",keywords:"@page @font-face"},{begin:"@",end:"[{;]",illegal:/:/,returnBegin:!0,contains:[{className:"keyword",begin:/@\-?\w[\w]*(\-\w+)*/},{begin:/\s/,endsWithParent:!0,excludeEnd:!0,relevance:0,keywords:"and or not only",contains:[{begin:/[a-z-]+:/,className:"attribute"},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.CSS_NUMBER_MODE]}]},{className:"selector-tag",begin:"[a-zA-Z-][a-zA-Z0-9_-]*",relevance:0},{begin:"{",end:"}",illegal:/\S/,contains:[e.C_BLOCK_COMMENT_MODE,n]}]}}}());hljs.registerLanguage("xml",function(){"use strict";return function(e){var n={className:"symbol",begin:"&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;"},a={begin:"\\s",contains:[{className:"meta-keyword",begin:"#?[a-z_][a-z1-9_-]+",illegal:"\\n"}]},s=e.inherit(a,{begin:"\\(",end:"\\)"}),t=e.inherit(e.APOS_STRING_MODE,{className:"meta-string"}),i=e.inherit(e.QUOTE_STRING_MODE,{className:"meta-string"}),c={endsWithParent:!0,illegal:/`]+/}]}]}]};return{name:"HTML, XML",aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"],case_insensitive:!0,contains:[{className:"meta",begin:"",relevance:10,contains:[a,i,t,s,{begin:"\\[",end:"\\]",contains:[{className:"meta",begin:"",contains:[a,s,i,t]}]}]},e.COMMENT("\x3c!--","--\x3e",{relevance:10}),{begin:"<\\!\\[CDATA\\[",end:"\\]\\]>",relevance:10},n,{className:"meta",begin:/<\?xml/,end:/\?>/,relevance:10},{className:"tag",begin:")",end:">",keywords:{name:"style"},contains:[c],starts:{end:"",returnEnd:!0,subLanguage:["css","xml"]}},{className:"tag",begin:")",end:">",keywords:{name:"script"},contains:[c],starts:{end:"<\/script>",returnEnd:!0,subLanguage:["javascript","handlebars","xml"]}},{className:"tag",begin:"",contains:[{className:"name",begin:/[^\/><\s]+/,relevance:0},c]}]}}}()); -------------------------------------------------------------------------------- /src/core/Skeletabs.js: -------------------------------------------------------------------------------- 1 | import debounce from 'lodash.debounce'; 2 | import { includes } from '../helpers/array'; 3 | import { findHiddenInTree, showInstantly, hideBack } from '../helpers/dom'; 4 | import { modulo, inRange } from '../helpers/math'; 5 | import { capitalize } from '../helpers/string'; 6 | import { getClassNames } from './classNames'; 7 | import { pushHistory, replaceHistory, getHashFromHistory } from './history'; 8 | import { nextId, getTimeId, setIds, unsetIds } from './ids'; 9 | import { parseKeyAction } from './keyboard'; 10 | import { processOptions } from './options'; 11 | import { getViewportWidth } from './viewport'; 12 | 13 | class Skeletabs { 14 | constructor(container, userOptions, userClassNames) { 15 | this.id = getTimeId(); 16 | this.container = container; 17 | this.options = processOptions(userOptions); 18 | this.classNames = getClassNames(userClassNames); 19 | this.disabledList = []; 20 | this.events = []; 21 | this.isInit = false; 22 | this.isActive = false; 23 | this.rotationId = undefined; 24 | } 25 | 26 | // Initialize 27 | init() { 28 | const { 29 | classNames, 30 | options: { breakpointLayout, resizeTimeout }, 31 | } = this; 32 | // Prevent duplicate call 33 | if (this.isInit) return; 34 | // Set flag on 35 | this.isInit = true; 36 | // Find DOM elements, make up extra necessary elements 37 | this.prepareElements(); 38 | // Activate unless alternative layout is set to 'none' 39 | if (!(breakpointLayout === 'destroy' && this.isBelowBreakpoint())) { 40 | this.activate(); 41 | // Dispatch event 42 | this.emit('skeletabs:init'); 43 | // Add init flag to the container 44 | this.$container.addClass(classNames.init); 45 | } 46 | // Update on viewport size change. This listener should be stored seperately, 47 | // because it should remain even after `deactivate()` process, 48 | // to be able to reactivate based on viewport size changes. 49 | this.unwatchResize = this.addEvent({ 50 | target: window, 51 | type: 'resize orientationchange', 52 | listener: debounce(() => { 53 | this.setLayout(); 54 | this.reload(); 55 | }, resizeTimeout), 56 | }); 57 | } 58 | 59 | // Reset & remove references to the current instance 60 | // so it can be garbadge collected. 61 | destroy() { 62 | // Prevent duplicate call 63 | if (!this.isInit) return; 64 | // Set flag off 65 | this.isInit = false; 66 | // Deactivate 67 | this.deactivate(); 68 | // Remove viewport resize listener 69 | this.unwatchResize(); 70 | // Dump unused data to avoid memory leak 71 | this.resetData(); 72 | } 73 | 74 | // Activate functionalities 75 | activate() { 76 | // Prevent duplicate call 77 | if (this.isActive) return; 78 | // Set flag on 79 | this.isActive = true; 80 | // Set accessibility DOM attributes 81 | this.setupAccessibility(); 82 | // Disable items according to user options 83 | this.disableByOptions(); 84 | // Calculate starting index 85 | this.defineStartIndex(); 86 | // Decide which layout to display 87 | this.setLayout(); 88 | // Update display related stuff (layout, panelHeight) 89 | this.reload(); 90 | // Add event listeners 91 | this.addEvents(); 92 | // hide all elements first 93 | this.$panels.css('display', 'none'); 94 | // { passive: true } prevents side effects (like hash change) 95 | this.show(this.currentIndex, { passive: true }); 96 | // Start rotation (on demand) 97 | this.options.autoplay && this.play(); 98 | // Dispatch event 99 | // this.emit('skeletabs:activate'); 100 | } 101 | 102 | // Dectivate functionalities while keeping necessary data for reactivation. 103 | deactivate() { 104 | // Prevent duplicate call 105 | if (!this.isActive) return; 106 | // Set flag off 107 | this.isActive = false; 108 | // Stop stream if any 109 | this.pause(); 110 | // Remove event listeners 111 | this.removeEvents(); 112 | // Reset all layout modifications 113 | this.resetLayout(); 114 | // Reset equalized panel heights 115 | this.resetPanelHeight(); 116 | // Reset element attributes 117 | this.resetElementAttributes(); 118 | // Dispatch event 119 | // this.emit('skeletabs:deactivate'); 120 | } 121 | 122 | // Dump unused data 123 | resetData() { 124 | this.disabledList = []; 125 | this.events = []; 126 | this.rotationId = undefined; 127 | delete this.$container; 128 | delete this.$tabGroup; 129 | delete this.$tabItems; 130 | delete this.$tabs; 131 | delete this.$panelGroup; 132 | delete this.$panels; 133 | delete this.$panelHeadings; 134 | delete this.size; 135 | delete this.tabIds; 136 | delete this.panelIds; 137 | delete this.startIndex; 138 | delete this.currentIndex; 139 | delete this.focusedIndex; 140 | delete this.currentLayout; 141 | } 142 | 143 | // Cache DOM elements and frequently accessed data 144 | prepareElements() { 145 | const { container, classNames } = this; 146 | // Use `:first` selector to prevent selecting nested skeletabs 147 | const $container = $(container); 148 | const $tabGroup = $container.find(`.${classNames.tabGroup}:first`); 149 | const $tabItems = $tabGroup.find(`> .${classNames.tabItem}`); 150 | const $tabs = $tabItems.find(`> .${classNames.tab}`); 151 | const $panelGroup = $container.find(`.${classNames.panelGroup}:first`); 152 | // Panels could NOT be direct children of panelGroup 153 | let $panels = $panelGroup.find(`.${classNames.panel}:first`); 154 | // (Can't .addBack() or .andSelf() because we are unsure of which ver. of jquery user shall use) 155 | $panels = $panels.add($panels.siblings(`.${classNames.panel}`)); 156 | // Find elements inside the container using `classNames` data 157 | this.$container = $container; 158 | this.$tabGroup = $tabGroup; 159 | this.$tabItems = $tabItems; 160 | this.$tabs = $tabs; 161 | this.$panelGroup = $panelGroup; 162 | this.$panels = $panels; 163 | // [error] Number of tabs/panels don't match 164 | if ($tabs.length !== $panels.length) { 165 | throw new Error( 166 | `Number of tabs and panels don't match: ${$container.selector}` 167 | ); 168 | } 169 | // Number of panels is useful for various calculations 170 | this.size = $panels.length; 171 | // Make accordion headings ahead of time (don't insert to DOM yet) 172 | if (this.options.breakpointLayout === 'accordion') { 173 | this.$panelHeadings = $tabs.map(() => { 174 | const div = document.createElement('div'); 175 | div.className = classNames.panelHeading; 176 | return div; 177 | }); 178 | } 179 | } 180 | 181 | // Add a new set of tab/panel 182 | add({ tab, panel }) { 183 | const { $tabGroup, $panelGroup, classNames, size } = this; 184 | // Pause existing stream for security 185 | this.pause(); 186 | // Generate new ids 187 | const tabId = nextId({ type: 'tab', prefix: 'skeletabsTab' }); 188 | const panelId = nextId({ type: 'panel', prefix: 'skeletabsPanel' }); 189 | // Create elements 190 | const $tabItem = $('
  • ', { 191 | class: classNames.tabItem, 192 | role: 'presentation', 193 | }); 194 | const $tab = $('