@ribajs/bs4
Advanced tools
| { | ||
| "language": "ts", | ||
| "collection": "@ribajs/schematics", | ||
| "sourceRoot": "src", | ||
| "templateEngine": "html", | ||
| "styleLanguage": "scss", | ||
| "component": { | ||
| "path": "components", | ||
| "flat": false | ||
| }, | ||
| "binder": { | ||
| "path": "binders", | ||
| "flat": true | ||
| }, | ||
| "formatter": { | ||
| "path": "formatters", | ||
| "flat": true | ||
| }, | ||
| "service": { | ||
| "path": "services", | ||
| "flat": true | ||
| } | ||
| } |
+3
-3
| { | ||
| "name": "@ribajs/bs4", | ||
| "description": "Bootstrap 4 module for Riba.js", | ||
| "version": "1.2.0", | ||
| "version": "1.2.1", | ||
| "author": "Pascal Garber <pascal@jumplink.eu>", | ||
@@ -44,3 +44,3 @@ "contributors": [], | ||
| "dependencies": { | ||
| "@ribajs/core": "^1.2.0", | ||
| "@ribajs/core": "^1.2.1", | ||
| "@types/jquery": "^3.3.31", | ||
@@ -55,2 +55,2 @@ "bootstrap": "^4.3.1", | ||
| "homepage": "https://github.com/ribajs/riba#readme" | ||
| } | ||
| } |
@@ -1,9 +0,11 @@ | ||
| <div class="col-12 tab-list nav nav-tabs nav-tabs-underline d-flex flex-nowrap" rv-id="'tab-list-' | append tab.handle" role="tablist"> | ||
| <div class="col-12 tab-list nav nav-tabs nav-tabs-underline d-flex flex-nowrap" role="tablist"> | ||
| <a class="nav-item nav-link d-inline-block text-truncate" | ||
| rv-each-tab="tabs" | ||
| rv-class-active="tab.active" | ||
| rv-href="'#tab-content-' | append tab.handle" | ||
| rv-add-class="'tab-title-' | append tab.handle" | ||
| rv-id="'tab-title-' | append tab.handle" | ||
| rv-aria-controls="'tab-content-' | append tab.handle" | ||
| rv-html="tab.title" | ||
| rv-on-click="activate" | ||
| rv-on-click="activate | args tab" | ||
| role="tab" | ||
@@ -13,11 +15,13 @@ aria-selected="false" | ||
| </div> | ||
| <div class="col-12 col-md-6 tab-content product-tab-content px-5 mx-auto"> | ||
| <div class="col-12 col-md-6 tab-content px-5 mx-auto"> | ||
| <div | ||
| class="tab-pane mt-3 mb-4" | ||
| rv-each-tab="tabs" | ||
| rv-class-show="tab.active" | ||
| rv-add-class="'tab-content-' | append tab.handle" | ||
| rv-id="'tab-content-' | append tab.handle" | ||
| rv-html="tab.content" | ||
| class="tab-pane mt-3 mb-4" | ||
| role="tabpanel" | ||
| aria-labelledby="'tab-title-' | append tab.handle" | ||
| rv-aria-labelledby="'tab-title-' | append tab.handle" | ||
| ></div> | ||
| </div> |
@@ -1,2 +0,2 @@ | ||
| import { Component, Debug, Binding } from '@ribajs/core'; | ||
| import { Component, Debug, Binding, handleizeFormatter } from '@ribajs/core'; | ||
| import template from './tabs.component.html'; | ||
@@ -8,2 +8,3 @@ | ||
| handle: string; | ||
| active: boolean; | ||
| } | ||
@@ -14,2 +15,3 @@ | ||
| activate: TabsComponent['activate']; | ||
| optionTabsAutoHeight: boolean; | ||
| } | ||
@@ -21,15 +23,16 @@ | ||
| protected debug = Debug('component:bs4-tabs'); | ||
| protected debug = Debug('component:' + TabsComponent.tagName); | ||
| protected scope: Scope = { | ||
| tabs: [], | ||
| tabs: new Array<Tab>(), | ||
| activate: this.activate, | ||
| optionTabsAutoHeight: false, | ||
| }; | ||
| private tabs: NodeListOf<Element>; | ||
| private tabPanes: NodeListOf<Element>; | ||
| private scrollable: Element | null; | ||
| private tabsSameHeight = true; | ||
| protected tabs?: NodeListOf<Element>; | ||
| protected tabPanes?: NodeListOf<Element>; | ||
| protected scrollable?: Element | null; | ||
| static get observedAttributes() { | ||
| return [ | ||
| 'option-tabs-auto-height', | ||
| 'tab-0-title', 'tab-0-content', 'tab-0-handle', | ||
@@ -61,46 +64,5 @@ 'tab-1-title', 'tab-1-content', 'tab-1-handle', | ||
| // Bind static template | ||
| this.tabs = this.el.querySelectorAll('[role="tab"]'); | ||
| this.tabPanes = this.el.querySelectorAll('.tab-pane'); | ||
| this.scrollable = this.el.querySelector('[scrollable]'); | ||
| this.debug('constructor', this.el, this.tabs, this.tabPanes); | ||
| this.tabs.forEach((tab => { | ||
| // TODO use `rv-on-click="activate"` instead? | ||
| tab.addEventListener('click', (event) => { | ||
| this.activateByTabElement(tab, event); | ||
| }); | ||
| tab.addEventListener('shown.bs.tab', (event) => { | ||
| const tab = (event.target || event.srcElement) as Element | null; | ||
| if (!tab) { | ||
| return; | ||
| } | ||
| if (this.scrollable) { | ||
| const tabScrollPosition = tab.getBoundingClientRect(); | ||
| const scrollLeftTo = this.scrollable.scrollLeft || 0 + tabScrollPosition.left; | ||
| // TODO animate | ||
| // this.scrollable.animate({ scrollLeft: scrollLeftTo}, 'slow'); | ||
| this.scrollable.scrollLeft = scrollLeftTo; | ||
| } | ||
| }); | ||
| tab.addEventListener('shown.bs.tab', (event) => { | ||
| if (this.scrollable) { | ||
| const tabScrollPosition = tab.getBoundingClientRect(); | ||
| const scrollLeftTo = this.scrollable.scrollLeft || 0 + tabScrollPosition.left; | ||
| // TODO animate | ||
| // this.$scrollable.animate({ scrollLeft: scrollLeftTo}, 'slow'); | ||
| this.scrollable.scrollLeft = scrollLeftTo; | ||
| } | ||
| }); | ||
| })); | ||
| if (this.tabsSameHeight) { | ||
| window.addEventListener('resize', () => { | ||
| this.setHeight(); | ||
| }); | ||
| } | ||
| this.addTabsByTemplate(); | ||
| this.initTabs(); | ||
| this.activateFirstTab(); | ||
| this.init(TabsComponent.observedAttributes); | ||
@@ -113,3 +75,12 @@ } | ||
| public setHeight() { | ||
| if (this.scope.optionTabsAutoHeight) { | ||
| return; | ||
| } | ||
| // Bind static template | ||
| this.setElements(); | ||
| let heigest = 0; | ||
| if (!this.tabPanes) { | ||
| return; | ||
| } | ||
| this.tabPanes.forEach((tabPane) => { | ||
@@ -139,100 +110,128 @@ if (!(tabPane as unknown as HTMLElement).style) { | ||
| public deactivateAll() { | ||
| // static | ||
| this.tabs.forEach((tabEl) => { | ||
| tabEl.classList.remove('active', 'show'); | ||
| }); | ||
| this.tabPanes.forEach((tabPaneEl) => { | ||
| tabPaneEl.classList.remove('active', 'show'); | ||
| }); | ||
| // dynamic | ||
| this.scope.tabs.forEach((tab) => { | ||
| const tabEl = this.el.querySelector('#tab-title-' + tab.handle); | ||
| const tabPaneEl = this.el.querySelector('#tab-content-' + tab.handle); | ||
| if (tabEl) tabEl.classList.remove('active', 'show'); | ||
| if (tabPaneEl) tabPaneEl.classList.remove('active', 'show'); | ||
| }); | ||
| for (const tab of this.scope.tabs) { | ||
| tab.active = false; | ||
| } | ||
| } | ||
| /** | ||
| * Used for static templates | ||
| */ | ||
| public activateByTabElement(tab: Element, event?: Event) { | ||
| public activate(tab: Tab, binding?: Binding, event?: Event) { | ||
| this.deactivateAll(); | ||
| tab.active = true; | ||
| this.debug('activate', event); | ||
| if (event) { | ||
| event.preventDefault(); | ||
| } | ||
| const target = tab.getAttribute('href'); | ||
| if (!target) { | ||
| console.warn('The href attribute to find the target is required!'); | ||
| return; | ||
| } | ||
| public activateFirstTab() { | ||
| if (this.scope.tabs.length > 0) { | ||
| this.activate(this.scope.tabs[0]); | ||
| } | ||
| const targetEl = this.el.querySelector(target); | ||
| if (!targetEl) { | ||
| console.warn(`Target not found with selector "${target}" not found!`); | ||
| } | ||
| protected setElements() { | ||
| this.tabs = this.el.querySelectorAll('[role="tab"]'); | ||
| this.tabPanes = this.el.querySelectorAll('[role="tabpanel"]'); | ||
| this.scrollable = this.el.querySelector('[scrollable]'); | ||
| } | ||
| protected resizeTabsArray(newSize: number) { | ||
| while (newSize > this.scope.tabs.length) { | ||
| this.scope.tabs.push({handle: '', title: '', content: '', active: false}); | ||
| } | ||
| this.scope.tabs.length = newSize; | ||
| } | ||
| protected onTabShownEventHandler(event: Event) { | ||
| const curTab = (event.target || event.srcElement) as Element | null; | ||
| if (!curTab) { | ||
| return; | ||
| } | ||
| this.debug('activate', target, targetEl); | ||
| this.deactivateAll(); | ||
| targetEl.classList.add('active'); | ||
| targetEl.classList.add('show'); | ||
| tab.classList.add("active"); | ||
| targetEl.dispatchEvent(new Event('shown.bs.tab')); | ||
| tab.dispatchEvent(new Event('shown.bs.tab')); | ||
| if (this.scrollable) { | ||
| const tabScrollPosition = curTab.getBoundingClientRect(); | ||
| const scrollLeftTo = this.scrollable.scrollLeft || 0 + tabScrollPosition.left; | ||
| // TODO animate | ||
| // this.scrollable.animate({ scrollLeft: scrollLeftTo}, 'slow'); | ||
| this.scrollable.scrollLeft = scrollLeftTo; | ||
| } | ||
| } | ||
| public activate(binding: Binding, event: Event, model: any, el: HTMLElement) { | ||
| this.activateByTabElement(el, event); | ||
| protected onResizeEventHandler(event: Event) { | ||
| this.setHeight(); | ||
| } | ||
| public activateByHandle(handle: string) { | ||
| const tabEl = this.el.querySelector('#tab-title-' + handle); | ||
| if (tabEl) { | ||
| this.activateByTabElement(tabEl); | ||
| protected initTabs() { | ||
| // Bind static template | ||
| this.setElements(); | ||
| this.debug('constructor', this.el, this.tabs, this.tabPanes); | ||
| if (this.tabs) { | ||
| this.tabs.forEach(((tab) => { | ||
| tab.removeEventListener('shown.bs.tab', this.onTabShownEventHandler); | ||
| tab.addEventListener('shown.bs.tab', this.onTabShownEventHandler); | ||
| })); | ||
| } | ||
| } | ||
| public activateFirstTab() { | ||
| const tabEl = this.el.querySelector('[role="tab"]'); | ||
| if (tabEl) { | ||
| this.activateByTabElement(tabEl); | ||
| if (this.scope.optionTabsAutoHeight) { | ||
| window.removeEventListener('resize', this.onResizeEventHandler.bind(this)); | ||
| window.addEventListener('resize', this.onResizeEventHandler.bind(this)); | ||
| this.setHeight(); | ||
| } | ||
| } | ||
| protected resizeTabs(newSize: number) { | ||
| while(newSize > this.scope.tabs.length) { | ||
| this.scope.tabs.push({handle: '', title: '', content: ''}); | ||
| protected addTabByAttribute(attributeName: string, newValue: string) { | ||
| this.debug('addTabByAttribute'); | ||
| const index = Number(attributeName.replace(/[^0-9]/g, '')); | ||
| this.debug('index', index); | ||
| if (index >= this.scope.tabs.length) { | ||
| this.resizeTabsArray(index + 1); | ||
| } | ||
| this.scope.tabs.length = newSize; | ||
| } | ||
| if (attributeName.endsWith('Content')) { | ||
| this.scope.tabs[index].content = newValue; | ||
| } | ||
| if (attributeName.endsWith('Title')) { | ||
| this.scope.tabs[index].title = newValue; | ||
| this.scope.tabs[index].handle = this.scope.tabs[index].handle || handleizeFormatter.read(this.scope.tabs[index].title); | ||
| } | ||
| if (attributeName.endsWith('Handle')) { | ||
| this.scope.tabs[index].handle = newValue; | ||
| } | ||
| this.debug('this.scope', this.scope); | ||
| // if is first tab | ||
| if ( | ||
| this.scope.tabs.length > 0 && | ||
| this.scope.tabs[0] && | ||
| this.scope.tabs[0].content.length > 0 && | ||
| this.scope.tabs[0].title.length > 0 && | ||
| this.scope.tabs[0].handle.length > 0 | ||
| ) { | ||
| this.activateFirstTab(); | ||
| } | ||
| } | ||
| protected addTabsByTemplate() { | ||
| const templates = this.el.querySelectorAll<HTMLTemplateElement>('template'); | ||
| templates.forEach((tpl) => { | ||
| const title = tpl.getAttribute('title'); | ||
| if (!title) { | ||
| console.error(new Error('template "title" attribute is required"')); | ||
| return; | ||
| } | ||
| const handle = tpl.getAttribute('handle') || handleizeFormatter.read(title); | ||
| if (!handle) { | ||
| console.error(new Error('template "handle" attribute is required"')); | ||
| return; | ||
| } | ||
| const content = tpl.innerHTML; | ||
| this.scope.tabs.push({title, handle, content, active: false}); | ||
| }); | ||
| } | ||
| protected parsedAttributeChangedCallback(attributeName: string, oldValue: any, newValue: any, namespace: string | null) { | ||
| super.parsedAttributeChangedCallback(attributeName, oldValue, newValue, namespace); | ||
| this.debug('parsedAttributeChangedCallback', attributeName); | ||
| if (attributeName.startsWith('tab')) { | ||
| const index = Number(attributeName.replace(/[^0-9]/g, '')); | ||
| this.debug('index', index); | ||
| if (index >= this.scope.tabs.length) { | ||
| this.resizeTabs(index + 1); | ||
| } | ||
| if (attributeName.endsWith('Content')) { | ||
| this.scope.tabs[index].content = newValue; | ||
| } | ||
| if (attributeName.endsWith('Title')) { | ||
| this.scope.tabs[index].title = newValue; | ||
| } | ||
| if (attributeName.endsWith('Handle')) { | ||
| this.scope.tabs[index].handle = newValue; | ||
| } | ||
| if ( | ||
| this.scope.tabs.length > 0 && | ||
| this.scope.tabs[0] && | ||
| this.scope.tabs[0].content.length > 0 && | ||
| this.scope.tabs[0].title.length > 0 && | ||
| this.scope.tabs[0].handle.length > 0 | ||
| ) { | ||
| this.activateFirstTab(); | ||
| } | ||
| if (this.tabsSameHeight) { | ||
| this.setHeight(); | ||
| } | ||
| this.addTabByAttribute(attributeName, newValue); | ||
| this.initTabs(); | ||
| } | ||
@@ -244,22 +243,27 @@ } | ||
| setTimeout(() => { | ||
| if (this.tabs.length > 0) { | ||
| this.activateFirstTab(); | ||
| } | ||
| if (this.tabsSameHeight) { | ||
| if (this.scope.optionTabsAutoHeight) { | ||
| this.setHeight(); | ||
| } | ||
| }, 200); | ||
| }, 500); | ||
| } | ||
| protected onlyTemplateChilds() { | ||
| let allAreTemplates: boolean = true; | ||
| this.el.childNodes.forEach((child) => { | ||
| this.debug('child', child); | ||
| allAreTemplates = allAreTemplates && (child.nodeName === 'TEMPLATE' || child.nodeName === '#text'); | ||
| }); | ||
| return allAreTemplates; | ||
| } | ||
| protected template() { | ||
| // Only set the component template if there no childs already | ||
| if (this.el.hasChildNodes()) { | ||
| // Only set the component template if there no childs or the childs are templates | ||
| if (!this.el.hasChildNodes() || this.onlyTemplateChilds()) { | ||
| this.debug('Use template', template); | ||
| return template; | ||
| } else { | ||
| this.debug('Do not use template, because element has child nodes'); | ||
| return null; | ||
| } else { | ||
| this.debug('Use template', template); | ||
| return template; | ||
| } | ||
| } | ||
| } |
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
39842
1.67%23
4.55%1114
2.3%Updated