├── .babelrc ├── .browserslistrc ├── .editorconfig ├── .eslintrc.js ├── .gitignore ├── .ignore ├── CHANGELOG.md ├── CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── package.json ├── pnpm-lock.yaml ├── src ├── components │ ├── AggregateStatusCard.vue │ ├── ApplicationLauncher.vue │ ├── Button.vue │ ├── Card.vue │ ├── CardNotification.vue │ ├── ColumnPicker.vue │ ├── Combobox.vue │ ├── Drawer.vue │ ├── DrawerGroup.vue │ ├── DrawerGroupAction.vue │ ├── DrawerNotification.vue │ ├── Dropdown.vue │ ├── EmptyChart.vue │ ├── ExpandCollapse.vue │ ├── FilterFields.vue │ ├── FilterResults.vue │ ├── Icon.ts │ ├── LauncherItem.vue │ ├── Layout.vue │ ├── ListGroupItem.vue │ ├── ListItem.vue │ ├── ListItemAdditionalInfo.vue │ ├── ListView.vue │ ├── MenuItem.ts │ ├── Modal.vue │ ├── Notification.vue │ ├── NotificationBell.vue │ ├── Notifications.vue │ ├── Option.vue │ ├── PaginateControl.vue │ ├── Popover.vue │ ├── RadioButton.vue │ ├── Select.vue │ ├── Sort.vue │ ├── Spinner.vue │ ├── Table.vue │ ├── TableRow.vue │ ├── Toggle.vue │ ├── Toolbar.vue │ ├── Tooltip.ts │ ├── UtilizationBarChart.vue │ ├── VerticalNavDivider.vue │ ├── VerticalSubmenu.vue │ ├── Void.ts │ └── index.ts ├── directives │ └── tooltip.ts ├── index.css ├── index.ts ├── mixins │ └── popupMixin.ts ├── ouia.ts ├── render.ts └── use.ts ├── tsconfig.json ├── typings └── global.d.ts └── vite.config.ts /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env", {"modules": false}] 4 | ], 5 | "plugins": [ 6 | "@babel/plugin-transform-regenerator", 7 | "@babel/plugin-transform-runtime", 8 | "@babel/plugin-transform-async-to-generator", 9 | "@babel/plugin-transform-destructuring", 10 | "@babel/plugin-proposal-object-rest-spread" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /.browserslistrc: -------------------------------------------------------------------------------- 1 | last 2 major versions 2 | not ie <= 10 3 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | browser: true, 5 | es2021: true, 6 | node: true, 7 | }, 8 | extends: [ 9 | 'plugin:vue/vue3-recommended', 10 | 'standard', 11 | '@vue/typescript/recommended', 12 | ], 13 | parserOptions: { 14 | ecmaVersion: 2021, 15 | }, 16 | rules: { 17 | 'no-console': process.env.NODE_ENV === 'production' 18 | ? 'warn' 19 | : 'off', 20 | 'no-debugger': process.env.NODE_ENV === 'production' 21 | ? 'warn' 22 | : 'off', 23 | 'vue/max-attributes-per-line': 'off', 24 | 'vue/html-closing-bracket-newline': 'off', 25 | 'vue/component-definition-name-casing': 'off', 26 | 'vue/require-default-prop': 'off', 27 | 'vue/multi-word-component-names': 'off', 28 | 'vue/singleline-html-element-content-newline': 'off', 29 | 'vue/multiline-html-element-content-newline': 'off', 30 | 'vue/valid-next-tick': 'off', 31 | 'space-before-function-paren': ['error', 'never'], 32 | semi: ['error', 'always'], 33 | 'comma-dangle': [ 34 | 'error', 35 | 'always-multiline', 36 | ], 37 | indent: ['error', 2, { CallExpression: { arguments: 'first' }, SwitchCase: 1 }], 38 | eqeqeq: 'off', 39 | }, 40 | }; 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.bak 2 | *.gz 3 | *.log 4 | *.map 5 | Thumbs.db 6 | .* 7 | !.ignore 8 | !.git* 9 | !.babelrc 10 | !.browserslistrc 11 | !.eslint* 12 | !.editorconfig 13 | node_modules 14 | /dist 15 | /docs 16 | -------------------------------------------------------------------------------- /.ignore: -------------------------------------------------------------------------------- 1 | /docs/docs.js 2 | pnpm-lock.yaml 3 | -------------------------------------------------------------------------------- /CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. 4 | 5 | We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality. 6 | 7 | Examples of unacceptable behavior by participants include: 8 | 9 | * The use of sexualized language or imagery 10 | * Personal attacks 11 | * Trolling or insulting/derogatory comments 12 | * Public or private harassment 13 | * Publishing other's private information, such as physical or electronic addresses, without explicit permission 14 | * Other unethical or unprofessional conduct. 15 | 16 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team. 17 | 18 | This code of conduct applies both within project spaces and in public spaces when an individual is representing the project or its community in a direct capacity. Personal views, beliefs and values of individuals do not necessarily reflect those of the organisation or affiliated individuals and organisations. 19 | 20 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers. 21 | 22 | This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0, available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/) 23 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are **welcome** and will be fully **credited**. 4 | 5 | We accept contributions via Pull Requests on [Github](https://github.com/mtorromeo/). 6 | 7 | 8 | ## Pull Requests 9 | 10 | - **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date. 11 | 12 | - **Consider our release cycle** - We try to follow [SemVer v2.0.0](http://semver.org/). Randomly breaking public APIs is not an option. 13 | 14 | - **Create feature branches** - Don't ask us to pull from your master branch. 15 | 16 | - **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. 17 | 18 | **Happy coding**! 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Massimiliano Torromeo 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vue PatternFly 3 2 | 3 | ![Passively Maintained](https://img.shields.io/badge/status-passively%20maintained-yellowgreen.svg?style=flat-square) 4 | [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) 5 | [![NPM](https://img.shields.io/npm/v/vue-patternfly.svg?style=flat-square)](https://npmjs.org/package/vue-patternfly) 6 | [![Downloads](https://img.shields.io/npm/dw/vue-patternfly.svg?style=flat-square)](https://npmjs.org/package/vue-patternfly) 7 | [![Issues](https://img.shields.io/github/issues/mtorromeo/vue-patternfly3.svg?style=flat-square)](https://github.com/mtorromeo/vue-patternfly3/issues) 8 | 9 | PatternFly 3 components for Vue 3. 10 | 11 | The project is in a **passively maintained** state and was formerly implemented for Vue 2 but has been ported to Vue 3 in order to facilitate the transition of applications to Vue 3 and subsequently to the new [PatternFly 4 version of this library](https://github.com/mtorromeo/vue-patternfly). Only bug fixes will be applied to this version as all development is now done on vue-patternfly. 12 | 13 | More informations on the project [homepage][link-homepage]. 14 | 15 | ### Installation 16 | ``` 17 | npm install --save vue-patternfly 18 | ``` 19 | 20 | or 21 | 22 | ``` 23 | yarn add vue-patternfly 24 | ``` 25 | 26 | ## Change log 27 | 28 | Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently. 29 | 30 | 36 | 37 | ## Contributing 38 | 39 | Please see [CONTRIBUTING](CONTRIBUTING.md) and [CONDUCT](CONDUCT.md) for details. 40 | 41 | ## Security 42 | 43 | If you discover any security related issues, please email massimiliano.torromeo@gmail.com instead of using the issue tracker. 44 | 45 | ## Credits 46 | 47 | - [Massimiliano Torromeo][link-author] 48 | - [All Contributors][link-contributors] 49 | 50 | ## License 51 | 52 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 53 | 54 | [link-homepage]: https://mtorromeo.github.io/vue-patternfly3 55 | [link-author]: https://github.com/mtorromeo 56 | [link-contributors]: ../../contributors 57 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-patternfly", 3 | "version": "0.3.9", 4 | "description": "PatternFly 3 components for Vue 3", 5 | "main": "dist/vue-patternfly.umd.js", 6 | "module": "dist/vue-patternfly.es.js", 7 | "typings": "dist/types/index.d.ts", 8 | "exports": { 9 | ".": { 10 | "import": "./dist/vue-patternfly.es.js", 11 | "require": "./dist/vue-patternfly.umd.js", 12 | "types": "./dist/types/index.d.ts" 13 | }, 14 | "./typings/global": { 15 | "types": "./typings/global.d.ts" 16 | }, 17 | "./vue-patternfly.css": "./dist/vue-patternfly.css" 18 | }, 19 | "directories": { 20 | "test": "test" 21 | }, 22 | "scripts": { 23 | "dev": "vite", 24 | "build": "vite build && vue-tsc --declaration --emitDeclarationOnly", 25 | "lint": "vue-tsc --noEmit", 26 | "prepack": "npm run build" 27 | }, 28 | "files": [ 29 | "dist/", 30 | "typings/global.d.ts" 31 | ], 32 | "keywords": [ 33 | "vue", 34 | "patternfly" 35 | ], 36 | "author": "Massimiliano Torromeo ", 37 | "license": "MIT", 38 | "repository": { 39 | "type": "git", 40 | "url": "git+https://github.com/mtorromeo/vue-patternfly.git" 41 | }, 42 | "bugs": { 43 | "url": "https://github.com/mtorromeo/vue-patternfly/issues" 44 | }, 45 | "homepage": "https://mtorromeo.github.io/vue-patternfly/", 46 | "dependencies": { 47 | "patternfly": "^3.59.1" 48 | }, 49 | "devDependencies": { 50 | "@types/lodash-es": "^4.17.5", 51 | "@vitejs/plugin-vue": "^5.0.4", 52 | "@vue/compiler-sfc": "^3.4.21", 53 | "@vue/eslint-config-prettier": "^9.0.0", 54 | "@vue/eslint-config-typescript": "^13.0.0", 55 | "eslint": "^8.57.0", 56 | "eslint-config-standard": "^17", 57 | "eslint-plugin-import": "^2.24.2", 58 | "eslint-plugin-node": "^11.1.0", 59 | "eslint-plugin-promise": "^6.0.0", 60 | "eslint-plugin-vue": "^9.23.0", 61 | "fs-extra": "^11.2.0", 62 | "lodash-es": "^4.17.21", 63 | "prettier": "^3.2.5", 64 | "resize-observer-polyfill": "^1.5.1", 65 | "sass": "^1.71.1", 66 | "vite": "^5.1.6", 67 | "vue": "^3.4.21", 68 | "vue-router": "^4.3.0", 69 | "vue-tsc": "^2.0.6" 70 | }, 71 | "peerDependencies": { 72 | "vue": "^3" 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/components/AggregateStatusCard.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 50 | 51 | 80 | -------------------------------------------------------------------------------- /src/components/ApplicationLauncher.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 68 | 69 | 78 | -------------------------------------------------------------------------------- /src/components/Button.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 183 | -------------------------------------------------------------------------------- /src/components/Card.vue: -------------------------------------------------------------------------------- 1 | 55 | 56 | 148 | -------------------------------------------------------------------------------- /src/components/CardNotification.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 33 | -------------------------------------------------------------------------------- /src/components/ColumnPicker.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 102 | 103 | 108 | -------------------------------------------------------------------------------- /src/components/Combobox.vue: -------------------------------------------------------------------------------- 1 | 63 | 64 | 327 | -------------------------------------------------------------------------------- /src/components/Drawer.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 75 | 76 | 93 | -------------------------------------------------------------------------------- /src/components/DrawerGroup.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 103 | 104 | 133 | -------------------------------------------------------------------------------- /src/components/DrawerGroupAction.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 32 | -------------------------------------------------------------------------------- /src/components/DrawerNotification.vue: -------------------------------------------------------------------------------- 1 | 50 | 51 | 131 | 132 | 140 | -------------------------------------------------------------------------------- /src/components/Dropdown.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 272 | -------------------------------------------------------------------------------- /src/components/EmptyChart.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 37 | 38 | 50 | -------------------------------------------------------------------------------- /src/components/ExpandCollapse.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 94 | 95 | 127 | -------------------------------------------------------------------------------- /src/components/FilterFields.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 150 | 151 | 181 | -------------------------------------------------------------------------------- /src/components/FilterResults.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 63 | -------------------------------------------------------------------------------- /src/components/Icon.ts: -------------------------------------------------------------------------------- 1 | // https://www.patternfly.org/v3/styles/icons/ 2 | // https://getbootstrap.com/docs/3.4/components/#glyphicons 3 | import { CSSProperties, defineComponent, h } from 'vue'; 4 | import { ouiaProps, useOUIAProps } from '../ouia'; 5 | 6 | export default defineComponent({ 7 | name: 'PfIcon', 8 | 9 | props: { 10 | name: String, 11 | src: String, 12 | tag: { 13 | type: String, 14 | default: 'span', 15 | }, 16 | ...ouiaProps, 17 | }, 18 | 19 | setup(props) { 20 | return useOUIAProps(props); 21 | }, 22 | 23 | render() { 24 | const classes: string[] = []; 25 | const style: CSSProperties = {}; 26 | if (this.src) { 27 | classes.push('pficon'); 28 | classes.push('pf-icon-img'); 29 | style.backgroundImage = `url("${this.src}")`; 30 | } else if (this.name) { 31 | const match = (/^(fa|pficon|glyphicon)-/).exec(this.name); 32 | if (match) { 33 | classes.push(match[1]); 34 | } 35 | if (this.name) { 36 | classes.push(this.name); 37 | } 38 | } 39 | 40 | return h(this.tag, { 41 | 'aria-hidden': 'true', 42 | class: classes, 43 | style, 44 | ...this.ouiaProps, 45 | }, this.$slots); 46 | }, 47 | }); 48 | -------------------------------------------------------------------------------- /src/components/LauncherItem.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 33 | -------------------------------------------------------------------------------- /src/components/Layout.vue: -------------------------------------------------------------------------------- 1 | 96 | 97 | 242 | 243 | 369 | -------------------------------------------------------------------------------- /src/components/ListGroupItem.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 138 | -------------------------------------------------------------------------------- /src/components/ListItem.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 64 | -------------------------------------------------------------------------------- /src/components/ListItemAdditionalInfo.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 56 | -------------------------------------------------------------------------------- /src/components/ListView.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | 110 | -------------------------------------------------------------------------------- /src/components/MenuItem.ts: -------------------------------------------------------------------------------- 1 | import { h, resolveComponent, mergeProps, defineComponent, Slot, DefineComponent, PropType, VNode } from 'vue'; 2 | import { renderSlot } from '../render'; 3 | import { ouiaProps, useOUIAProps } from '../ouia'; 4 | import type { RouteLocationRaw } from 'vue-router'; 5 | 6 | export default defineComponent({ 7 | name: 'PfMenuItem', 8 | 9 | inheritAttrs: false, 10 | 11 | props: { 12 | title: String, 13 | to: { 14 | type: [String, Object] as PropType, 15 | default: undefined, 16 | }, 17 | replace: Boolean, 18 | activeClass: { 19 | type: String, 20 | default: 'active', 21 | }, 22 | ariaCurrentValue: String, 23 | exactActiveClass: String, 24 | icon: String, 25 | badge: String, 26 | href: String, 27 | target: String, 28 | vertical: Boolean, 29 | ...ouiaProps, 30 | }, 31 | 32 | setup(props) { 33 | return useOUIAProps(props); 34 | }, 35 | 36 | render() { 37 | let tag: string | DefineComponent = this.to ? 'router-link' : 'li'; 38 | 39 | const linkBuilder = (href: string | undefined) => { 40 | const linkChildren = []; 41 | 42 | if (this.icon) { 43 | linkChildren.push(h(resolveComponent('pf-icon'), { 44 | name: this.icon, 45 | title: this.title, 46 | })); 47 | } 48 | 49 | linkChildren.push(h('span', { 50 | class: 'list-group-item-value', 51 | }, this.title)); 52 | 53 | if (this.badge) { 54 | linkChildren.push(h('div', { 55 | class: 'badge-container-pf', 56 | }, [ 57 | h('div', { class: 'badge' }, this.badge), 58 | ])); 59 | } 60 | 61 | return h('a', { 62 | href, 63 | target: this.target, 64 | }, linkChildren); 65 | }; 66 | 67 | const slot: Slot = (href?: string) => { 68 | let elements: VNode[] = []; 69 | const children: VNode[] | Slot = renderSlot(this.$slots.default, []); 70 | if (children) { 71 | if (this.vertical) { 72 | elements = children; 73 | } else { 74 | elements.push(h('ul', { 75 | class: 'nav navbar-nav navbar-persistent', 76 | }, children)); 77 | } 78 | } 79 | 80 | elements.unshift(linkBuilder(this.href || href)); 81 | return elements; 82 | }; 83 | 84 | let tagProps = mergeProps({ 85 | class: 'list-group-item', 86 | ...this.ouiaProps, 87 | }, this.$attrs); 88 | 89 | if (tag !== 'router-link') { 90 | return h(tag, tagProps, slot); 91 | } 92 | 93 | tag = resolveComponent('router-link') as DefineComponent; 94 | const liProps = { ...tagProps }; 95 | tagProps = { 96 | custom: true, 97 | to: this.to, 98 | replace: this.replace, 99 | activeClass: this.activeClass, 100 | ariaCurrentValue: this.ariaCurrentValue, 101 | exactActiveClass: this.exactActiveClass, 102 | }; 103 | 104 | const routerSlot: Slot = ({ navigate, href }: { navigate: () => void, href: string }) => { 105 | return [h('li', { onClick: navigate, ...liProps }, slot(href))]; 106 | }; 107 | 108 | return h(tag, tagProps, routerSlot); 109 | }, 110 | }); 111 | -------------------------------------------------------------------------------- /src/components/Modal.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 143 | 144 | 170 | -------------------------------------------------------------------------------- /src/components/Notification.vue: -------------------------------------------------------------------------------- 1 | 56 | 57 | 206 | 207 | 215 | -------------------------------------------------------------------------------- /src/components/NotificationBell.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 34 | -------------------------------------------------------------------------------- /src/components/Notifications.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 72 | -------------------------------------------------------------------------------- /src/components/Option.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 82 | -------------------------------------------------------------------------------- /src/components/PaginateControl.vue: -------------------------------------------------------------------------------- 1 | 59 | 60 | 175 | 176 | 181 | -------------------------------------------------------------------------------- /src/components/Popover.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 67 | -------------------------------------------------------------------------------- /src/components/RadioButton.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 131 | -------------------------------------------------------------------------------- /src/components/Select.vue: -------------------------------------------------------------------------------- 1 | 69 | 70 | 239 | 240 | 293 | -------------------------------------------------------------------------------- /src/components/Sort.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 155 | 156 | 169 | -------------------------------------------------------------------------------- /src/components/Spinner.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 49 | -------------------------------------------------------------------------------- /src/components/Table.vue: -------------------------------------------------------------------------------- 1 | 109 | 110 | 366 | 367 | 402 | -------------------------------------------------------------------------------- /src/components/TableRow.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 62 | -------------------------------------------------------------------------------- /src/components/Toggle.vue: -------------------------------------------------------------------------------- 1 | 61 | 62 | 228 | 229 | 242 | -------------------------------------------------------------------------------- /src/components/Toolbar.vue: -------------------------------------------------------------------------------- 1 | 79 | 80 | 270 | 271 | 325 | -------------------------------------------------------------------------------- /src/components/Tooltip.ts: -------------------------------------------------------------------------------- 1 | import popupMixin from '../mixins/popupMixin'; 2 | import { defineComponent, h } from 'vue'; 3 | import { ouiaProps, useOUIAProps } from '../ouia'; 4 | import { renderSlot } from '../render'; 5 | 6 | export default defineComponent({ 7 | name: 'PfTooltip', 8 | 9 | mixins: [popupMixin], 10 | 11 | props: { 12 | text: { 13 | type: String, 14 | default: '', 15 | }, 16 | ...ouiaProps, 17 | }, 18 | 19 | setup(props) { 20 | return useOUIAProps(props); 21 | }, 22 | 23 | data(this: void) { 24 | return { 25 | name: 'tooltip', 26 | }; 27 | }, 28 | 29 | computed: { 30 | allContent() { 31 | return this.text; 32 | }, 33 | }, 34 | 35 | methods: { 36 | isNotEmpty() { 37 | return !!this.text; 38 | }, 39 | }, 40 | 41 | render() { 42 | return h( 43 | this.tag, 44 | this.ouiaProps, 45 | [ 46 | renderSlot(this.$slots.default), 47 | h('div', 48 | { 49 | ref: 'popup', 50 | role: 'tooltip', 51 | onMouseleave: this.hideOnLeave, 52 | }, 53 | [ 54 | h('div', { class: 'tooltip-arrow' }), 55 | h('div', { 56 | class: 'tooltip-inner', 57 | innerHTML: this.text, 58 | }), 59 | ], 60 | ), 61 | ], 62 | ); 63 | }, 64 | }); 65 | -------------------------------------------------------------------------------- /src/components/UtilizationBarChart.vue: -------------------------------------------------------------------------------- 1 | 92 | 93 | 171 | 172 | 185 | -------------------------------------------------------------------------------- /src/components/VerticalNavDivider.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 40 | 41 | 104 | -------------------------------------------------------------------------------- /src/components/VerticalSubmenu.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | 125 | -------------------------------------------------------------------------------- /src/components/Void.ts: -------------------------------------------------------------------------------- 1 | import { DefineComponent, defineComponent, PropType, Slot, VNode } from 'vue'; 2 | 3 | export default defineComponent({ 4 | name: 'Void', 5 | 6 | inheritAttrs: false, 7 | 8 | props: { 9 | alter: { 10 | type: Function as PropType<(v: VNode[]) => VNode[]>, 11 | default: null, 12 | }, 13 | 14 | useRef: { 15 | type: Object as PropType, 16 | default: null, 17 | }, 18 | 19 | template: Boolean, 20 | }, 21 | 22 | setup() { 23 | return { 24 | templateFn: undefined as Slot | undefined, 25 | }; 26 | }, 27 | 28 | render() { 29 | if (this.template) { 30 | this.templateFn = this.$slots.default; 31 | return []; 32 | } 33 | 34 | if (this.useRef && this.useRef.templateFn) { 35 | return this.useRef.templateFn(); 36 | } 37 | 38 | if (!this.$slots.default) { 39 | return; 40 | } 41 | 42 | if (!this.$slots.default) { 43 | return []; 44 | } 45 | 46 | let children = this.$slots.default(); 47 | if (this.alter) { 48 | children = this.alter(children); 49 | } 50 | 51 | return children; 52 | }, 53 | }); 54 | -------------------------------------------------------------------------------- /src/components/index.ts: -------------------------------------------------------------------------------- 1 | export { default as AggregateStatusCard, default as PfAggregateStatusCard } from './AggregateStatusCard.vue'; 2 | export { default as ApplicationLauncher, default as PfApplicationLauncher } from './ApplicationLauncher.vue'; 3 | export { default as Button, default as PfButton } from './Button.vue'; 4 | export { default as Card, default as PfCard } from './Card.vue'; 5 | export { default as CardNotification, default as PfCardNotification } from './CardNotification.vue'; 6 | export { default as ColumnPicker, default as PfColumnPicker } from './ColumnPicker.vue'; 7 | export { default as Combobox, default as PfCombobox } from './Combobox.vue'; 8 | export { default as Drawer, default as PfDrawer } from './Drawer.vue'; 9 | export { default as DrawerGroup, default as PfDrawerGroup } from './DrawerGroup.vue'; 10 | export { default as DrawerGroupAction, default as PfDrawerGroupAction } from './DrawerGroupAction.vue'; 11 | export { default as DrawerNotification, default as PfDrawerNotification } from './DrawerNotification.vue'; 12 | export { default as Dropdown, default as PfDropdown } from './Dropdown.vue'; 13 | export { default as EmptyChart, default as PfEmptyChart } from './EmptyChart.vue'; 14 | export { default as ExpandCollapse, default as PfExpandCollapse } from './ExpandCollapse.vue'; 15 | export { default as FilterFields, default as PfFilterFields } from './FilterFields.vue'; 16 | export { default as FilterResults, default as PfFilterResults } from './FilterResults.vue'; 17 | export { default as Icon, default as PfIcon } from './Icon'; 18 | export { default as LauncherItem, default as PfLauncherItem } from './LauncherItem.vue'; 19 | export { default as Layout, default as PfLayout } from './Layout.vue'; 20 | export { default as ListGroupItem, default as PfListGroupItem } from './ListGroupItem.vue'; 21 | export { default as ListItem, default as PfListItem } from './ListItem.vue'; 22 | export { default as ListItemAdditionalInfo, default as PfListItemAdditionalInfo } from './ListItemAdditionalInfo.vue'; 23 | export { default as ListView, default as PfListView } from './ListView.vue'; 24 | export { default as MenuItem, default as PfMenuItem } from './MenuItem'; 25 | export { default as Modal, default as PfModal } from './Modal.vue'; 26 | export { default as Notification, default as PfNotification } from './Notification.vue'; 27 | export { default as NotificationBell, default as PfNotificationBell } from './NotificationBell.vue'; 28 | export { default as Notifications, default as PfNotifications } from './Notifications.vue'; 29 | export { default as Option, default as PfOption } from './Option.vue'; 30 | export { default as PaginateControl, default as PfPaginateControl } from './PaginateControl.vue'; 31 | export { default as Popover, default as PfPopover } from './Popover.vue'; 32 | export { default as RadioButton, default as PfRadioButton } from './RadioButton.vue'; 33 | export { default as Select, default as PfSelect } from './Select.vue'; 34 | export { default as Sort, default as PfSort } from './Sort.vue'; 35 | export { default as Spinner, default as PfSpinner } from './Spinner.vue'; 36 | export { default as Table, default as PfTable } from './Table.vue'; 37 | export { default as TableRow, default as PfTableRow } from './TableRow.vue'; 38 | export { default as Toggle, default as PfToggle } from './Toggle.vue'; 39 | export { default as Toolbar, default as PfToolbar } from './Toolbar.vue'; 40 | export { default as Tooltip, default as PfTooltip } from './Tooltip'; 41 | export { default as UtilizationBarChart, default as PfUtilizationBarChart } from './UtilizationBarChart.vue'; 42 | export { default as VerticalNavDivider, default as PfVerticalNavDivider } from './VerticalNavDivider.vue'; 43 | export { default as VerticalSubmenu, default as PfVerticalSubmenu } from './VerticalSubmenu.vue'; 44 | export { default as Void } from './Void'; 45 | -------------------------------------------------------------------------------- /src/directives/tooltip.ts: -------------------------------------------------------------------------------- 1 | import Tooltip from '../components/Tooltip'; 2 | import { App, createApp, DefineComponent, defineComponent, DirectiveBinding, h, ObjectDirective } from 'vue'; 3 | import { TooltipPlacement, TooltipTrigger } from '../mixins/popupMixin'; 4 | 5 | function isTooltipPlacement(value: string): value is TooltipPlacement { 6 | return /(top)|(left)|(right)|(bottom)/.test(value); 7 | } 8 | 9 | function isTooltipTrigger(value: string): value is TooltipTrigger { 10 | return /(hover)|(focus)|(click)/.test(value); 11 | } 12 | 13 | function getContainer() { 14 | const cont = document.createElement('div'); 15 | document.documentElement.appendChild(cont); 16 | return cont; 17 | } 18 | 19 | function created(el: (Element | DefineComponent) & { 'v-tooltip'?: App, container?: HTMLElement }, binding: DirectiveBinding) { 20 | unmounted(el); 21 | const options = []; 22 | for (const key in binding.modifiers) { 23 | if (Object.prototype.hasOwnProperty.call(binding.modifiers, key) && binding.modifiers[key]) { 24 | options.push(key); 25 | } 26 | } 27 | 28 | const props: { 29 | placement?: TooltipPlacement; 30 | trigger?: TooltipTrigger; 31 | enterable?: boolean; 32 | } = {}; 33 | options.forEach(option => { 34 | if (isTooltipPlacement(option)) { 35 | props.placement = option; 36 | } else if (isTooltipTrigger(option)) { 37 | props.trigger = option; 38 | } else if (/unenterable/.test(option)) { 39 | props.enterable = false; 40 | } 41 | }); 42 | 43 | const container = getContainer(); 44 | const vm = createApp(defineComponent({ 45 | render() { 46 | return h(Tooltip as unknown as DefineComponent, { 47 | target: el, 48 | appendTo: binding.arg && '#' + binding.arg, 49 | text: typeof binding.value === 'string' ? (binding.value && binding.value.toString()) : (binding.value && binding.value.text && binding.value.text.toString()), 50 | viewport: binding.value && binding.value.viewport && binding.value.viewport.toString(), 51 | customClass: binding.value && binding.value.customClass && binding.value.customClass.toString(), 52 | showDelay: binding.value && binding.value.showDelay, 53 | hideDelay: binding.value && binding.value.hideDelay, 54 | ...props, 55 | }); 56 | }, 57 | })); 58 | vm.mount(container); 59 | el.container = container; 60 | el['v-tooltip'] = vm; 61 | } 62 | 63 | function unmounted(el: (Element | DefineComponent) & { 'v-tooltip'?: App, container?: HTMLElement }) { 64 | const vm = el['v-tooltip']; 65 | if (!vm) { 66 | return; 67 | } 68 | 69 | const { container } = el; 70 | if (container instanceof HTMLElement) { 71 | document.documentElement.removeChild(container); 72 | } 73 | vm.unmount(); 74 | delete el['v-tooltip']; 75 | delete el.container; 76 | } 77 | 78 | function updated(el: (Element | DefineComponent) & { 'v-tooltip'?: App, container: HTMLElement }, binding: DirectiveBinding) { 79 | if (binding.value !== binding.oldValue) { 80 | created(el, binding); 81 | } 82 | } 83 | 84 | const directive: ObjectDirective = { 85 | created, 86 | updated, 87 | unmounted, 88 | }; 89 | 90 | export default directive; 91 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | /* Scrollbars */ 2 | ::-webkit-scrollbar { 3 | transition: width .2s linear; 4 | width: 6px; 5 | height: 6px; 6 | } 7 | 8 | ::-webkit-scrollbar-thumb { 9 | transition: background-color .2s linear; 10 | background-color: #aaa; 11 | background-color: rgba(170,170,170,.5); 12 | border-radius: 4px; 13 | } 14 | 15 | ::-webkit-scrollbar-thumb:hover { 16 | background-color: #999; 17 | background-color: rgba(153,153,153,.7); 18 | } 19 | 20 | ::-webkit-scrollbar-track { 21 | background-color: transparent; 22 | } 23 | 24 | html { 25 | /* https://drafts.csswg.org/css-scrollbars-1/ */ 26 | scrollbar-width: thin; 27 | scrollbar-color: rgba(170,170,170,.5) transparent; 28 | } 29 | 30 | /* Dropdown */ 31 | .dropdown-kebab-pf .caret { 32 | height: 1em; 33 | vertical-align: inherit; 34 | width: 3px; 35 | } 36 | 37 | .dropdown-kebab-pf .dropdown-menu { 38 | left: -5px; 39 | } 40 | 41 | .dropdown-kebab-pf .dropdown-menu.dropdown-menu-right { 42 | margin-left: 11px; 43 | } 44 | 45 | .dropdown-kebab-pf .caret:before { 46 | font-family: FontAwesome; 47 | content: "\f142"; 48 | line-height: 1; 49 | } 50 | 51 | /* Labels */ 52 | .label a.pf-remove-button { 53 | color: #fff; 54 | display: inline-block; 55 | margin-left: 5px; 56 | } 57 | 58 | /* Icon-image */ 59 | .pf-icon-img { 60 | background-repeat: no-repeat; 61 | background-size: contain; 62 | width: 1em; 63 | height: 1em; 64 | display: inline-block; 65 | vertical-align: middle; 66 | } 67 | 68 | /* Cards */ 69 | .card-pf-heading-no-bottom { 70 | margin: 0 -20px 0px; 71 | padding: 0 20px 0; 72 | } 73 | 74 | .card-pf-icon-image { 75 | height: 18px; 76 | margin: 0 5px 5px; 77 | } 78 | 79 | .utilization-trend-chart-pf .donut-chart-pf { 80 | width: 100%; 81 | float: left; 82 | padding-top: 15px; 83 | } 84 | 85 | .utilization-trend-chart-pf h3 { 86 | font-weight: 400; 87 | } 88 | 89 | .utilization-trend-chart-pf .current-values { 90 | border-bottom: 1px solid #d1d1d1; 91 | float: left; 92 | padding: 0 5px 10px 0; 93 | width: 100%; 94 | } 95 | 96 | .utilization-trend-chart-pf .available-count { 97 | margin: 3px 0; 98 | padding-left: 0; 99 | padding-right: 5px; 100 | } 101 | 102 | .utilization-trend-chart-pf .available-text { 103 | font-size: 12px; 104 | font-weight: 400; 105 | line-height: 14px; 106 | margin: 2px 0; 107 | padding: 0 5px; 108 | } 109 | 110 | .utilization-trend-chart-pf .radial-chart { 111 | float: left; 112 | padding-top: 10px; 113 | width: 100%; 114 | } 115 | 116 | .utilization-trend-chart-pf .sparkline-chart { 117 | float: left; 118 | margin-left: -5px; 119 | margin-right: -5px; 120 | width: 100%; 121 | } 122 | 123 | .utilization-trend-chart-pf .legend-text { 124 | color: inherit; 125 | display: block; 126 | font-size: 12px; 127 | font-weight: 400; 128 | margin-left: 0; 129 | } 130 | 131 | .utilization-trend-chart-pf.data-unavailable-pf .current-values { 132 | color: transparent; 133 | } 134 | 135 | .card-view-pf { 136 | overflow: auto; 137 | padding-top: 20px; 138 | padding-left: 2px; 139 | } 140 | 141 | .card-view-pf .card { 142 | -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, .175); 143 | background: #fff; 144 | border-top: 2px solid transparent; 145 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.175); 146 | display: block; 147 | float: left; 148 | height: 290px; 149 | margin-right: 20px; 150 | margin-bottom: 20px; 151 | padding: 10px; 152 | position: relative; 153 | text-align: center; 154 | width: 260px; 155 | } 156 | 157 | .card-view-pf .card .card-check-box { 158 | left: 10px; 159 | position: absolute; 160 | top: 8px; 161 | width: 20px; 162 | z-index: 3; 163 | visibility: hidden; 164 | } 165 | 166 | .card-view-pf .card-content { 167 | height: 100%; 168 | margin: 2px 0 10px 0; 169 | overflow: auto; 170 | width: 100%; 171 | } 172 | 173 | .card-view-pf .card-title { 174 | color: #1186C1; 175 | font-weight: 500; 176 | font-size: 16px; 177 | line-height: 1.1; 178 | margin-top: 0px; 179 | } 180 | 181 | .card-view-pf .card.active, 182 | .card-view-pf .card.active:hover, 183 | .card-view-pf .card.active:focus { 184 | border: solid 3px #00a8e1; 185 | } 186 | 187 | .card-view-pf .card:hover, 188 | .card-view-pf .card:focus { 189 | -webkit-box-shadow: 0px 3px 10px -2px rgba(0,0,0,0.24); 190 | -moz-box-shadow: 0px 3px 10px -2px rgba(0,0,0,0.24); 191 | box-shadow: 0px 3px 10px -2px rgba(0,0,0,0.24); 192 | border: 1px solid #d1d1d1 193 | } 194 | 195 | .card-view-pf .card.active .pficon, 196 | .card-view-pf .card.active:hover .pficon, 197 | .card-view-pf .card.active:focus .pficon { 198 | color: #ffffff; 199 | } 200 | .card-view-pf .card:hover .card-check-box, 201 | .card-view-pf .card.active .card-check-box { 202 | visibility: visible; 203 | } 204 | 205 | .card-view-pf .card.disabled, .card.disabled:hover, .card.disabled:focus { 206 | border: 1px solid #eee; 207 | color: #999999; 208 | cursor: not-allowed; 209 | -webkit-box-shadow: none; 210 | -moz-box-shadow: none; 211 | box-shadow: none; 212 | } 213 | 214 | .trend-card-large-pf .trend-header-pf { 215 | font-size: 16px; 216 | font-weight: 400; 217 | display: block; 218 | margin-left: 10px; 219 | } 220 | 221 | .trend-card-small-pf .trend-header-pf { 222 | font-size: 12px; 223 | font-weight: 400; 224 | display: block; 225 | margin-left: 10px; 226 | } 227 | 228 | .trend-card-large-pf .trend-title-big-pf { 229 | font-size: 26px; 230 | font-weight: 300; 231 | margin-left: 10px; 232 | } 233 | 234 | .trend-card-small-pf .trend-title-big-pf { 235 | font-size: 17px; 236 | font-weight: 400; 237 | margin-left: 10px; 238 | } 239 | 240 | .trend-card-large-pf .trend-title-small-pf { 241 | font-size: 12px; 242 | font-weight: 400; 243 | } 244 | 245 | .trend-card-small-pf .trend-title-small-pf { 246 | font-size: 10px; 247 | font-weight: 400; 248 | } 249 | 250 | .trend-flat-details { 251 | display: table; 252 | margin-top: 5px; 253 | } 254 | 255 | @media (min-width: 768px) { 256 | .trend-flat-details { 257 | margin-top: 25px; 258 | } 259 | } 260 | 261 | .trend-flat-details-cell { 262 | display: table-cell; 263 | vertical-align: bottom; 264 | min-width: 70px; 265 | } 266 | 267 | .trend-header-compact-pf { 268 | display: block; 269 | font-size: 12px; 270 | font-weight: 400; 271 | } 272 | 273 | .trend-title-compact-big-pf { 274 | font-size: 36px; 275 | font-weight: 300; 276 | line-height: 1; 277 | } 278 | 279 | .trend-title-compact-small-pf { 280 | font-size: 12px; 281 | font-weight: 400; 282 | } 283 | 284 | .trend-title-flat-big-pf { 285 | font-size: 26px; 286 | font-weight: 300; 287 | line-height: 1; 288 | margin-right: 15px; 289 | } 290 | 291 | .trend-label-flat-pf { 292 | font-size: 12px; 293 | font-weight: 400; 294 | line-height: 1; 295 | } 296 | 297 | .trend-label-flat-strong-pf { 298 | display: block; 299 | font-size: 12px; 300 | font-weight: 700; 301 | line-height: 1; 302 | } 303 | 304 | .trend-footer-pf { 305 | font-size: 10px; 306 | font-weight: 400; 307 | color: #333; 308 | margin-left: 10px; 309 | } 310 | 311 | .data-unavailable-pf [class*="trend-title-"], .data-unavailable-pf [class*="trend-label-"] { 312 | color: transparent; 313 | } 314 | 315 | .data-unavailable-pf .trend-footer-pf { 316 | color: transparent; 317 | } 318 | 319 | .input-group .input-group-btn .dropdown-menu > .selected > a { 320 | background-color: #0099d3 !important; 321 | border-color: #0076b7 !important; 322 | color: #fff !important; 323 | } 324 | 325 | /* Heap map */ 326 | .heatmap-pf-container { 327 | position: relative; 328 | } 329 | 330 | .heatmap-pf-container-pf .loading { 331 | position: absolute; 332 | top: 100px; 333 | right: 50%; 334 | z-index: 10; 335 | } 336 | 337 | .heatmap-pf-container .heatmap-container { 338 | margin-left: -1px; 339 | } 340 | 341 | .heatmap-pf-svg { 342 | width: 100%; 343 | height: 100%; 344 | } 345 | 346 | .heatmap-pf-legend-container{ 347 | list-style-type: none; 348 | margin-top: 5px; 349 | padding: 0; 350 | overflow: auto; 351 | } 352 | 353 | .heatmap-pf-legend-items { 354 | float: left; 355 | } 356 | 357 | .legend-pf-color-box { 358 | width: 11px; 359 | height: 11px; 360 | margin-left: 5px; 361 | margin-right: 5px; 362 | display: inline-block; 363 | } 364 | .legend-pf-color-box:first-of-type { 365 | margin-left: 0px; 366 | } 367 | 368 | .legend-pf-text { 369 | font-size: 11px; 370 | font-weight: 400; 371 | line-height: 11px; 372 | padding-right: 5px; 373 | } 374 | 375 | /* Angular bootstrap classes differ when opened rather than when collapsed */ 376 | accordion > .panel-group .panel-default .panel-title > a:before { 377 | content: "\f105"; 378 | } 379 | accordion > .panel-group .panel-open .panel-title > a:before { 380 | content: "\f107"; 381 | } 382 | 383 | .wizard-pf-footer .tooltip-wrapper { 384 | border: none; 385 | box-shadow: none; 386 | display: inline-block; 387 | margin-left: 5px; 388 | padding: 0; 389 | text-align: center; 390 | } 391 | 392 | .wizard-pf-footer .tooltip-wrapper .btn[disabled] { 393 | pointer-events: none; 394 | } 395 | 396 | .wizard-pf-singlestep { 397 | margin-left: 0; 398 | } 399 | 400 | .wizard-pf-position-override { 401 | position: relative; 402 | } 403 | 404 | .wizard-pf-footer-inline { 405 | text-align: left; 406 | } 407 | 408 | .wizard-pf-cancel-inline { 409 | margin-left: 25px; 410 | } 411 | 412 | .pf-expand-placeholder { 413 | margin-right: 10px; 414 | } 415 | 416 | .login-pf-page .login-pf-brand { 417 | width: auto !important; 418 | } 419 | 420 | .btn.btn-flat { 421 | box-shadow: none; 422 | } 423 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { App } from 'vue'; 2 | 3 | import './index.css'; 4 | import * as components from './components'; 5 | import VTooltip from './directives/tooltip'; 6 | 7 | // Declare install function executed by Vue.use() 8 | export function install(app: App) { 9 | const registered: string[] = []; 10 | for (const [name, component] of Object.entries(components)) { 11 | const componentName = component.name || name; 12 | if (!registered.includes(componentName)) { 13 | app.component(componentName, component); 14 | registered.push(componentName); 15 | } 16 | } 17 | // also register old name for backward compatibility 18 | app.component('PfToastNotification', components.PfNotification); 19 | app.directive('tooltip', VTooltip); 20 | } 21 | 22 | // Create module definition for Vue.use() 23 | const plugin = { 24 | install, 25 | }; 26 | 27 | // To allow use as module (npm/webpack/etc.) export components 28 | export * from './components'; 29 | export { default as VTooltip } from './directives/tooltip'; 30 | export * as ouia from './ouia'; 31 | 32 | export default plugin; 33 | -------------------------------------------------------------------------------- /src/mixins/popupMixin.ts: -------------------------------------------------------------------------------- 1 | import { DefineComponent, defineComponent, PropType, Ref, ref } from 'vue'; 2 | 3 | export type TooltipPlacement = 'top' | 'bottom' | 'left' | 'right'; 4 | export type TooltipTrigger = 'hover' | 'focus' | 'click' | 'hover-focus' | 'outside-click'; 5 | 6 | export function getViewportSize() { 7 | const width = Math.max(document.documentElement.clientWidth, window.innerWidth || 0); 8 | const height = Math.max(document.documentElement.clientHeight, window.innerHeight || 0); 9 | return { 10 | width, 11 | height, 12 | }; 13 | } 14 | 15 | export function isAvailableAtPosition(trigger: HTMLElement, popup: HTMLElement, placement: TooltipPlacement) { 16 | const triggerRect = trigger.getBoundingClientRect(); 17 | const popupRect = popup.getBoundingClientRect(); 18 | const viewPortSize = getViewportSize(); 19 | let top = true; 20 | let right = true; 21 | let bottom = true; 22 | let left = true; 23 | switch (placement) { 24 | case 'top': 25 | top = triggerRect.top >= popupRect.height; 26 | left = triggerRect.left + triggerRect.width / 2 >= popupRect.width / 2; 27 | right = triggerRect.right - triggerRect.width / 2 + popupRect.width / 2 <= viewPortSize.width; 28 | break; 29 | case 'bottom': 30 | bottom = triggerRect.bottom + popupRect.height <= viewPortSize.height; 31 | left = triggerRect.left + triggerRect.width / 2 >= popupRect.width / 2; 32 | right = triggerRect.right - triggerRect.width / 2 + popupRect.width / 2 <= viewPortSize.width; 33 | break; 34 | case 'right': 35 | right = triggerRect.right + popupRect.width <= viewPortSize.width; 36 | top = triggerRect.top + triggerRect.height / 2 >= popupRect.height / 2; 37 | bottom = triggerRect.bottom - triggerRect.height / 2 + popupRect.height / 2 <= viewPortSize.height; 38 | break; 39 | case 'left': 40 | left = triggerRect.left >= popupRect.width; 41 | top = triggerRect.top + triggerRect.height / 2 >= popupRect.height / 2; 42 | bottom = triggerRect.bottom - triggerRect.height / 2 + popupRect.height / 2 <= viewPortSize.height; 43 | break; 44 | } 45 | return top && right && bottom && left; 46 | } 47 | 48 | export function setTooltipPosition(tooltip: HTMLElement, trigger: HTMLElement, placement: TooltipPlacement, auto: boolean, appendToSelector: string | null, viewport: string | ((trigger: HTMLElement) => Element)) { 49 | const isPopover = tooltip && tooltip.className && tooltip.className.indexOf('popover') >= 0; 50 | let containerScrollTop; 51 | let containerScrollLeft; 52 | if (!appendToSelector || appendToSelector === 'body') { 53 | const doc = document.documentElement; 54 | containerScrollLeft = (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0); 55 | containerScrollTop = (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0); 56 | } else { 57 | const container = document.querySelector(appendToSelector); 58 | if (!container) { 59 | return; 60 | } 61 | containerScrollLeft = container.scrollLeft; 62 | containerScrollTop = container.scrollTop; 63 | } 64 | // auto adjust placement 65 | if (auto) { 66 | // Try: right -> bottom -> left -> top 67 | // Cause the default placement is top 68 | const placements: TooltipPlacement[] = ['right', 'bottom', 'left', 'top']; 69 | // The class switch helper function 70 | const changePlacementClass = (placement: TooltipPlacement) => { 71 | // console.log(placement) 72 | placements.forEach(placement => { 73 | tooltip.classList.remove(placement); 74 | }); 75 | tooltip.classList.add(placement); 76 | }; 77 | // No need to adjust if the default placement fits 78 | if (!isAvailableAtPosition(trigger, tooltip, placement)) { 79 | for (let i = 0, l = placements.length; i < l; i++) { 80 | // Re-assign placement class 81 | changePlacementClass(placements[i]); 82 | // Break if new placement fits 83 | if (isAvailableAtPosition(trigger, tooltip, placements[i])) { 84 | placement = placements[i]; 85 | break; 86 | } 87 | } 88 | changePlacementClass(placement); 89 | } 90 | } 91 | // fix left and top for tooltip 92 | const rect = trigger.getBoundingClientRect(); 93 | const tooltipRect = tooltip.getBoundingClientRect(); 94 | let top; 95 | let left; 96 | if (placement === 'bottom') { 97 | top = containerScrollTop + rect.top + rect.height; 98 | left = containerScrollLeft + rect.left + rect.width / 2 - tooltipRect.width / 2; 99 | } else if (placement === 'left') { 100 | top = containerScrollTop + rect.top + rect.height / 2 - tooltipRect.height / 2; 101 | left = containerScrollLeft + rect.left - tooltipRect.width; 102 | } else if (placement === 'right') { 103 | top = containerScrollTop + rect.top + rect.height / 2 - tooltipRect.height / 2; 104 | // https://github.com/wxsms/uiv/issues/272 105 | // add 1px to fix above issue 106 | left = containerScrollLeft + rect.left + rect.width + 1; 107 | } else { 108 | top = containerScrollTop + rect.top - tooltipRect.height; 109 | left = containerScrollLeft + rect.left + rect.width / 2 - tooltipRect.width / 2; 110 | } 111 | let viewportEl; 112 | // viewport option 113 | if (typeof viewport === 'string') { 114 | viewportEl = document.querySelector(viewport); 115 | } else if (typeof viewport === 'function') { 116 | viewportEl = viewport(trigger); 117 | } 118 | if (viewportEl instanceof Element) { 119 | const popoverFix = isPopover ? 11 : 0; 120 | const viewportReact = viewportEl.getBoundingClientRect(); 121 | const viewportTop = containerScrollTop + viewportReact.top; 122 | const viewportLeft = containerScrollLeft + viewportReact.left; 123 | const viewportBottom = viewportTop + viewportReact.height; 124 | const viewportRight = viewportLeft + viewportReact.width; 125 | // fix top 126 | if (top < viewportTop) { 127 | top = viewportTop; 128 | } else if (top + tooltipRect.height > viewportBottom) { 129 | top = viewportBottom - tooltipRect.height; 130 | } 131 | // fix left 132 | if (left < viewportLeft) { 133 | left = viewportLeft; 134 | } else if (left + tooltipRect.width > viewportRight) { 135 | left = viewportRight - tooltipRect.width; 136 | } 137 | // fix for popover pointer 138 | if (placement === 'bottom') { 139 | top -= popoverFix; 140 | } else if (placement === 'left') { 141 | left += popoverFix; 142 | } else if (placement === 'right') { 143 | left -= popoverFix; 144 | } else { 145 | top += popoverFix; 146 | } 147 | } 148 | // set position finally 149 | tooltip.style.top = `${top}px`; 150 | tooltip.style.left = `${left}px`; 151 | } 152 | 153 | export default defineComponent({ 154 | props: { 155 | modelValue: { 156 | type: Boolean, 157 | default: false, 158 | }, 159 | tag: { 160 | type: String, 161 | default: 'span', 162 | }, 163 | trigger: { 164 | type: String as PropType, 165 | default: 'hover-focus', 166 | }, 167 | placement: { 168 | type: String as PropType, 169 | default: 'top', 170 | }, 171 | autoPlacement: { 172 | type: Boolean, 173 | default: true, 174 | }, 175 | appendTo: { 176 | type: String, 177 | default: 'body', 178 | }, 179 | transitionDuration: { 180 | type: Number, 181 | default: 150, 182 | }, 183 | hideDelay: { 184 | type: Number, 185 | default: 0, 186 | }, 187 | showDelay: { 188 | type: Number, 189 | default: 0, 190 | }, 191 | enable: { 192 | type: Boolean, 193 | default: true, 194 | }, 195 | enterable: { 196 | type: Boolean, 197 | default: true, 198 | }, 199 | target: { 200 | type: [Object, String] as PropType, 201 | default: null, 202 | }, 203 | viewport: { 204 | type: [Function, String] as PropType Element)>, 205 | default: null, 206 | }, 207 | customClass: String, 208 | }, 209 | 210 | emits: { 211 | 'update:modelValue': (value: boolean) => value !== undefined, 212 | input: (value: boolean) => value !== undefined, 213 | show: () => true, 214 | hide: () => true, 215 | }, 216 | 217 | setup() { 218 | const triggerEl: Ref = ref(null); 219 | return { 220 | triggerEl, 221 | }; 222 | }, 223 | 224 | data(this: void) { 225 | return { 226 | name: '', 227 | hideTimeoutId: 0, 228 | showTimeoutId: 0, 229 | transitionTimeoutId: 0, 230 | autoTimeoutId: 0, 231 | }; 232 | }, 233 | 234 | watch: { 235 | modelValue(v) { 236 | v ? this.show() : this.hide(); 237 | }, 238 | 239 | trigger() { 240 | this.clearListeners(); 241 | this.initListeners(); 242 | }, 243 | 244 | target(value) { 245 | this.clearListeners(); 246 | this.initTriggerElByTarget(value); 247 | this.initListeners(); 248 | }, 249 | 250 | allContent() { 251 | // can not use value because it can not detect slot changes 252 | if (this.isNotEmpty()) { 253 | // reset position while content changed & is shown 254 | // nextTick is required 255 | this.$nextTick(() => { 256 | if (this.isShown()) { 257 | this.resetPosition(); 258 | } 259 | }); 260 | } else { 261 | this.hide(); 262 | } 263 | }, 264 | 265 | enable(value) { 266 | // hide if enable changed to false 267 | if (!value) { 268 | this.hide(); 269 | } 270 | }, 271 | }, 272 | 273 | mounted() { 274 | if (this.$refs.popup instanceof HTMLElement && this.$refs.popup.parentNode) { 275 | this.$refs.popup.parentNode.removeChild(this.$refs.popup); 276 | } 277 | this.$nextTick(() => { 278 | this.initTriggerElByTarget(this.target); 279 | this.initListeners(); 280 | if (this.modelValue) { 281 | this.show(); 282 | } 283 | }); 284 | }, 285 | 286 | beforeUnmount() { 287 | this.clearListeners(); 288 | if (this.$refs.popup instanceof HTMLElement && this.$refs.popup.parentNode) { 289 | this.$refs.popup.parentNode.removeChild(this.$refs.popup); 290 | } 291 | }, 292 | 293 | methods: { 294 | isNotEmpty() { 295 | return false; 296 | }, 297 | 298 | initTriggerElByTarget(target: string | HTMLElement | DefineComponent) { 299 | if (target) { 300 | // target exist 301 | if (typeof target === 'string') { // is selector 302 | this.triggerEl = document.querySelector(target) as HTMLElement; 303 | } else if (target instanceof HTMLElement) { // is element 304 | this.triggerEl = target; 305 | } else if (target.$el instanceof HTMLElement) { // is component 306 | this.triggerEl = target.$el; 307 | } 308 | } else { 309 | // find special element 310 | const trigger = this.$el.querySelector('[data-role="trigger"]'); 311 | if (trigger) { 312 | this.triggerEl = trigger; 313 | } else { 314 | // use the first child 315 | const firstChild = this.$el.firstChild; 316 | this.triggerEl = firstChild === this.$refs.popup ? null : firstChild; 317 | } 318 | } 319 | }, 320 | 321 | initListeners() { 322 | if (this.triggerEl) { 323 | if (this.trigger === 'hover') { 324 | this.triggerEl.addEventListener('mouseenter', this.show); 325 | this.triggerEl.addEventListener('mouseleave', this.hide); 326 | } else if (this.trigger === 'focus') { 327 | this.triggerEl.addEventListener('focus', this.show); 328 | this.triggerEl.addEventListener('blur', this.hide); 329 | } else if (this.trigger === 'hover-focus') { 330 | this.triggerEl.addEventListener('mouseenter', this.handleAuto); 331 | this.triggerEl.addEventListener('mouseleave', this.handleAuto); 332 | this.triggerEl.addEventListener('focus', this.handleAuto); 333 | this.triggerEl.addEventListener('blur', this.handleAuto); 334 | } else if (this.trigger === 'click' || this.trigger === 'outside-click') { 335 | this.triggerEl.addEventListener('click', this.toggle); 336 | } 337 | } 338 | window.addEventListener('click', this.windowClicked); 339 | }, 340 | 341 | clearListeners() { 342 | if (this.triggerEl) { 343 | this.triggerEl.removeEventListener('focus', this.show); 344 | this.triggerEl.removeEventListener('blur', this.hide); 345 | this.triggerEl.removeEventListener('mouseenter', this.show); 346 | this.triggerEl.removeEventListener('mouseleave', this.hide); 347 | this.triggerEl.removeEventListener('click', this.toggle); 348 | this.triggerEl.removeEventListener('mouseenter', this.handleAuto); 349 | this.triggerEl.removeEventListener('mouseleave', this.handleAuto); 350 | this.triggerEl.removeEventListener('focus', this.handleAuto); 351 | this.triggerEl.removeEventListener('blur', this.handleAuto); 352 | } 353 | window.removeEventListener('click', this.windowClicked); 354 | this.clearTimeouts(); 355 | }, 356 | 357 | clearTimeouts() { 358 | if (this.hideTimeoutId) { 359 | clearTimeout(this.hideTimeoutId); 360 | this.hideTimeoutId = 0; 361 | } 362 | if (this.showTimeoutId) { 363 | clearTimeout(this.showTimeoutId); 364 | this.showTimeoutId = 0; 365 | } 366 | if (this.transitionTimeoutId) { 367 | clearTimeout(this.transitionTimeoutId); 368 | this.transitionTimeoutId = 0; 369 | } 370 | if (this.autoTimeoutId) { 371 | clearTimeout(this.autoTimeoutId); 372 | this.autoTimeoutId = 0; 373 | } 374 | }, 375 | 376 | resetPosition() { 377 | const popup = this.$refs.popup; 378 | if (!(popup instanceof HTMLElement) || !this.triggerEl) { 379 | return; 380 | } 381 | setTooltipPosition(popup, this.triggerEl, this.placement, this.autoPlacement, this.appendTo, this.viewport); 382 | }, 383 | 384 | hideOnLeave() { 385 | if (this.trigger === 'hover' || (this.trigger === 'hover-focus' && !this.triggerEl?.matches(':focus'))) { 386 | this.$hide(); 387 | } 388 | }, 389 | 390 | toggle() { 391 | if (this.isShown()) { 392 | this.hide(); 393 | } else { 394 | this.show(); 395 | } 396 | }, 397 | 398 | show() { 399 | if (this.enable && this.triggerEl && this.isNotEmpty() && !this.isShown()) { 400 | const popUpAppendedContainer = this.hideTimeoutId > 0; // weird condition 401 | if (popUpAppendedContainer) { 402 | clearTimeout(this.hideTimeoutId); 403 | this.hideTimeoutId = 0; 404 | } 405 | if (this.transitionTimeoutId > 0) { 406 | clearTimeout(this.transitionTimeoutId); 407 | this.transitionTimeoutId = 0; 408 | } 409 | clearTimeout(this.showTimeoutId); 410 | this.showTimeoutId = setTimeout(() => { 411 | this.showTimeoutId = 0; 412 | const popup = this.$refs.popup; 413 | if (popup instanceof HTMLElement) { 414 | const alreadyOpenModalNum = document.querySelectorAll('.modal-backdrop').length; 415 | if (alreadyOpenModalNum > 1) { 416 | const defaultZ = this.name === 'popover' ? 1060 : 1070; 417 | const offset = (alreadyOpenModalNum - 1) * 20; 418 | popup.style.zIndex = `${defaultZ + offset}`; 419 | } 420 | // add to dom 421 | if (!popUpAppendedContainer) { 422 | popup.className = `${this.name} ${this.placement} ${this.customClass ? this.customClass : ''} fade`; 423 | const container = document.querySelector(this.appendTo); 424 | container?.appendChild(popup); 425 | this.resetPosition(); 426 | } 427 | popup.classList.add('in'); 428 | this.$emit('update:modelValue', true); 429 | this.$emit('input', true); 430 | this.$emit('show'); 431 | } 432 | }, this.showDelay); 433 | } 434 | }, 435 | 436 | hide() { 437 | if (this.showTimeoutId > 0) { 438 | clearTimeout(this.showTimeoutId); 439 | this.showTimeoutId = 0; 440 | } 441 | 442 | if (!this.isShown()) { 443 | return; 444 | } 445 | if (this.enterable && (this.trigger === 'hover' || this.trigger === 'hover-focus')) { 446 | clearTimeout(this.hideTimeoutId); 447 | this.hideTimeoutId = setTimeout(() => { 448 | this.hideTimeoutId = 0; 449 | const popup = this.$refs.popup; 450 | if (popup instanceof HTMLElement && !popup.matches(':hover')) { 451 | this.$hide(); 452 | } 453 | }, 100); 454 | } else { 455 | this.$hide(); 456 | } 457 | }, 458 | 459 | $hide() { 460 | if (this.isShown()) { 461 | clearTimeout(this.hideTimeoutId); 462 | this.hideTimeoutId = setTimeout(() => { 463 | this.hideTimeoutId = 0; 464 | if (this.$refs.popup instanceof HTMLElement) { 465 | this.$refs.popup.classList.remove('in'); 466 | } 467 | // gives fade out time 468 | this.transitionTimeoutId = setTimeout(() => { 469 | this.transitionTimeoutId = 0; 470 | if (this.$refs.popup instanceof HTMLElement && this.$refs.popup.parentNode) { 471 | this.$refs.popup.parentNode.removeChild(this.$refs.popup); 472 | } 473 | this.$emit('update:modelValue', false); 474 | this.$emit('input', false); 475 | this.$emit('hide'); 476 | }, this.transitionDuration); 477 | }, this.hideDelay); 478 | } 479 | }, 480 | 481 | isShown() { 482 | return this.$refs.popup instanceof HTMLElement && this.$refs.popup.classList.contains('in'); 483 | }, 484 | 485 | windowClicked(event: MouseEvent) { 486 | if (this.trigger !== 'outside-click' || !(event.target instanceof Node) || !(this.triggerEl instanceof HTMLElement) || !(this.$refs.popup instanceof HTMLElement)) { 487 | return; 488 | } 489 | if (!this.triggerEl.contains(event.target) && !this.$refs.popup.contains(event.target) && this.isShown()) { 490 | this.hide(); 491 | } 492 | }, 493 | 494 | handleAuto() { 495 | clearTimeout(this.autoTimeoutId); 496 | this.autoTimeoutId = setTimeout(() => { 497 | this.autoTimeoutId = 0; 498 | if (this.triggerEl?.matches(':hover, :focus')) { 499 | this.show(); 500 | } else { 501 | this.hide(); 502 | } 503 | }, 20); // 20ms make firefox happy 504 | }, 505 | }, 506 | }); 507 | -------------------------------------------------------------------------------- /src/ouia.ts: -------------------------------------------------------------------------------- 1 | import { getCurrentInstance, ComputedRef, computed, unref } from 'vue'; 2 | 3 | export interface OUIAProps { 4 | ouiaId?: string; 5 | ouiaSafe?: boolean; 6 | } 7 | 8 | let uid = 0; 9 | const ouiaPrefix = 'OUIA-Generated-'; 10 | const ouiaIdByRoute: Record = {}; 11 | 12 | export const ouiaProps = { 13 | ouiaId: { 14 | type: String, 15 | default: null as string | null, 16 | }, 17 | ouiaSafe: Boolean, 18 | }; 19 | 20 | export function useOUIAProps(props: OUIAProps, { 21 | name = null as string | null | undefined, 22 | variant = null as string | null, 23 | safe = null as boolean | ComputedRef | null, 24 | } = {}) { 25 | if (name === null) { 26 | const instance = getCurrentInstance(); 27 | name = instance?.proxy?.$options.name?.replace(/^pf-|^Pf/, ''); 28 | } 29 | 30 | return { 31 | ouiaProps: computed(() => ({ 32 | 'data-ouia-component-type': `V-PF3/${name}`, 33 | 'data-ouia-safe': unref(safe) ?? props.ouiaSafe, 34 | 'data-ouia-component-id': props.ouiaId || getDefaultOUIAId(name, variant), 35 | })), 36 | }; 37 | } 38 | 39 | /** 40 | * Returns a generated id based on the URL location 41 | * 42 | * @param {string} name OUIA component type 43 | * @param {string} variant Optional variant to add to the generated ID 44 | */ 45 | export function getDefaultOUIAId(name: string | null = null, variant: string | null = null) { 46 | try { 47 | const key = `${window.location.href}-${name}-${variant || ''}`; 48 | if (!ouiaIdByRoute[key]) { 49 | ouiaIdByRoute[key] = 0; 50 | } 51 | return `${ouiaPrefix}${name}-${variant ? `${variant}-` : ''}${++ouiaIdByRoute[key]}`; 52 | } catch (exception) { 53 | return `${ouiaPrefix}${name}-${variant ? `${variant}-` : ''}${++uid}`; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/render.ts: -------------------------------------------------------------------------------- 1 | import { Slot, VNode } from 'vue'; 2 | 3 | export function renderSlot(slot: Slot | undefined, fallback: VNode[] = []) { 4 | // slot can be an array in vue compat mode 2 5 | if (Array.isArray(slot)) { 6 | return slot as ReturnType; 7 | } 8 | return slot === undefined ? fallback : slot() || fallback; 9 | } 10 | -------------------------------------------------------------------------------- /src/use.ts: -------------------------------------------------------------------------------- 1 | import { provide, inject, ref, onMounted, onBeforeUnmount, nextTick, getCurrentInstance, Component, InjectionKey, Ref } from 'vue'; 2 | 3 | export function tryOnMounted(hook: () => unknown, sync = true) { 4 | if (getCurrentInstance()) { 5 | onMounted(hook); 6 | } else if (sync) { 7 | hook(); 8 | } else { 9 | nextTick(hook); 10 | } 11 | } 12 | 13 | type ChildrenTracker = { 14 | register: (item: T) => void; 15 | unregister: (item: T) => void; 16 | } 17 | 18 | export type ChildrenTrackerInjectionKey = InjectionKey>; 19 | 20 | const ChildrenTrackerSymbol = Symbol('Children tracker provide/inject symbol') as ChildrenTrackerInjectionKey; 21 | 22 | export function provideChildrenTracker(symbol: symbol | ChildrenTrackerInjectionKey) { 23 | const items = ref([]) as Ref; 24 | 25 | provide(symbol || ChildrenTrackerSymbol, { 26 | register: (item: T) => items.value.push(item), 27 | 28 | unregister: (item: T) => { 29 | const idx = items.value.findIndex(i => i === item); 30 | if (idx >= 0) { 31 | items.value.splice(idx, 1); 32 | } 33 | }, 34 | }); 35 | 36 | return items; 37 | } 38 | 39 | export function useChildrenTracker(symbol: symbol | ChildrenTrackerInjectionKey) { 40 | const tracker = inject(symbol || ChildrenTrackerSymbol, null); 41 | 42 | if (tracker) { 43 | tryOnMounted(() => { 44 | const instance = getCurrentInstance(); 45 | if (instance?.proxy) { 46 | tracker.register(instance.proxy); 47 | } 48 | }); 49 | 50 | onBeforeUnmount(() => { 51 | const instance = getCurrentInstance(); 52 | if (instance?.proxy) { 53 | tracker.unregister(instance.proxy); 54 | } 55 | }); 56 | } 57 | 58 | return tracker; 59 | } 60 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "rootDir": "./src", 4 | "outDir": "dist/types", 5 | "types": [ 6 | "vite/client" 7 | ], 8 | "lib": [ 9 | "es2015", 10 | "es2016", 11 | "es2017", 12 | "es2018", 13 | "es2019", 14 | "es2020", 15 | "dom", 16 | "dom.iterable" 17 | ], 18 | "target": "es2020", 19 | "module": "es2020", 20 | "declaration": true, 21 | "moduleResolution": "node", 22 | "jsx": "preserve", 23 | "esModuleInterop": true, 24 | "skipLibCheck": true, 25 | "importHelpers": true, 26 | "strict": true, 27 | "strictNullChecks": true, 28 | "allowJs": true, 29 | "useDefineForClassFields": true, 30 | "sourceMap": true, 31 | "resolveJsonModule": true 32 | }, 33 | "include": [ 34 | "src/**/*" 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /typings/global.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'vue' { 2 | export interface GlobalComponents { 3 | PfAggregateStatusCard: typeof import('vue-patternfly').PfAggregateStatusCard; 4 | PfApplicationLauncher: typeof import('vue-patternfly').PfApplicationLauncher; 5 | PfButton: typeof import('vue-patternfly').PfButton; 6 | PfCard: typeof import('vue-patternfly').PfCard; 7 | PfCardNotification: typeof import('vue-patternfly').PfCardNotification; 8 | PfColumnPicker: typeof import('vue-patternfly').PfColumnPicker; 9 | PfCombobox: typeof import('vue-patternfly').PfCombobox; 10 | PfDrawer: typeof import('vue-patternfly').PfDrawer; 11 | PfDrawerGroup: typeof import('vue-patternfly').PfDrawerGroup; 12 | PfDrawerGroupAction: typeof import('vue-patternfly').PfDrawerGroupAction; 13 | PfDrawerNotification: typeof import('vue-patternfly').PfDrawerNotification; 14 | PfDropdown: typeof import('vue-patternfly').PfDropdown; 15 | PfEmptyChart: typeof import('vue-patternfly').PfEmptyChart; 16 | PfExpandCollapse: typeof import('vue-patternfly').PfExpandCollapse; 17 | PfFilterFields: typeof import('vue-patternfly').PfFilterFields; 18 | PfFilterResults: typeof import('vue-patternfly').PfFilterResults; 19 | PfIcon: typeof import('vue-patternfly').PfIcon; 20 | PfLauncherItem: typeof import('vue-patternfly').PfLauncherItem; 21 | PfLayout: typeof import('vue-patternfly').PfLayout; 22 | PfListGroupItem: typeof import('vue-patternfly').PfListGroupItem; 23 | PfListItem: typeof import('vue-patternfly').PfListItem; 24 | PfListItemAdditionalInfo: typeof import('vue-patternfly').PfListItemAdditionalInfo; 25 | PfListView: typeof import('vue-patternfly').PfListView; 26 | PfMenuItem: typeof import('vue-patternfly').PfMenuItem; 27 | PfModal: typeof import('vue-patternfly').PfModal; 28 | PfNotification: typeof import('vue-patternfly').PfNotification; 29 | PfNotificationBell: typeof import('vue-patternfly').PfNotificationBell; 30 | PfNotifications: typeof import('vue-patternfly').PfNotifications; 31 | PfOption: typeof import('vue-patternfly').PfOption; 32 | PfPaginateControl: typeof import('vue-patternfly').PfPaginateControl; 33 | PfPopover: typeof import('vue-patternfly').PfPopover; 34 | PfRadioButton: typeof import('vue-patternfly').PfRadioButton; 35 | PfSelect: typeof import('vue-patternfly').PfSelect; 36 | PfSort: typeof import('vue-patternfly').PfSort; 37 | PfSpinner: typeof import('vue-patternfly').PfSpinner; 38 | PfTable: typeof import('vue-patternfly').PfTable; 39 | PfTableRow: typeof import('vue-patternfly').PfTableRow; 40 | PfToggle: typeof import('vue-patternfly').PfToggle; 41 | PfToolbar: typeof import('vue-patternfly').PfToolbar; 42 | PfTooltip: typeof import('vue-patternfly').PfTooltip; 43 | PfUtilizationBarChart: typeof import('vue-patternfly').PfUtilizationBarChart; 44 | PfVerticalNavDivider: typeof import('vue-patternfly').PfVerticalNavDivider; 45 | PfVerticalSubmenu: typeof import('vue-patternfly').PfVerticalSubmenu; 46 | Void: typeof import('vue-patternfly').Void; 47 | } 48 | } 49 | 50 | export {}; 51 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { defineConfig } from 'vite'; 3 | import vue from '@vitejs/plugin-vue'; 4 | 5 | // https://vitejs.dev/config/ 6 | export default defineConfig({ 7 | build: { 8 | lib: { 9 | entry: path.resolve(__dirname, 'src/index.ts'), 10 | name: 'VuePatternfly', 11 | fileName: format => `vue-patternfly.${format}.js`, 12 | }, 13 | rollupOptions: { 14 | external: ['vue'], 15 | output: { 16 | exports: 'named', 17 | assetFileNames: 'vue-patternfly.[ext]', 18 | globals: { 19 | vue: 'Vue', 20 | }, 21 | }, 22 | }, 23 | }, 24 | resolve: { 25 | dedupe: ['vue'], 26 | }, 27 | css: { 28 | preprocessorOptions: { 29 | scss: { 30 | quietDeps: true, 31 | }, 32 | }, 33 | }, 34 | plugins: [vue()], 35 | }); 36 | --------------------------------------------------------------------------------