diff --git a/README.md b/README.md index bb07ae5..e2d2063 100644 --- a/README.md +++ b/README.md @@ -11,17 +11,17 @@ To initialize this component for all supported browsers, you must add scripts to - + ``` -#### Minimal HTML Content (for SEO)* -This is the HTML required to be on page before Google can scan the page for their indexing algorithm. This is usually achieved via `prerendering` or `server side rendering (SSR)`. None of the JavaScript or CSS for the `` is necessary for the page to be indexed. +#### HTML Content (for SEO)* +This is the HTML required to be on page before Google/Bing can scan the page for their indexing algorithms. This is usually achieved via 'prerendering' or 'server side rendering (SSR)'. None of the JavaScript or CSS for the `` is necessary for the page to be indexed. No content is required by default to be indexed by search engines but here is an HTML example of how you can add content that will be indexed. ```html - First Item Title + panel title
- First Item content + panel content
``` @@ -29,36 +29,53 @@ This is the HTML required to be on page before Google can scan the page for thei ### Theming Set these values to get different themes. +**Custom Properties** | **Variable Name** | **Default Value** | | --- | --- | -| `--ps-thang` | `var(--blah, #3777bc)` | +| `--ps--border-color` | `#bcbcbc` | +| `--ps--tab-border-color` | `rgba(255, 255, 255, 0)` | +| `--ps--tab-border-color--active` | `rgba(255, 255, 255, 1)` | +| `--ps--tab-text-color` | `inherit` | + +**Shadow Parts (*)** +| **Part Name** | **Summary** | +| --- | --- | +| `ps-summary` | | +| `ps-details` | | +| `ps-tabs` | | +| `ps-panels` | | + +(*) Not Supported in Webkit based browsers ### Attributes -| **name** | **summary** | **expected value** | -| --- | --- | --- | -| `active-index` | 'active-index' is something cool | an integer as a string | +none + ### Slots | **name** | **summary** | **type** | **SEO Requirement** | | --- | --- | --- | --- | -| `ps:tab-*` | These are the main navigation links | dynamic | ✅ | -| `ps:panel-*` | These are the main navigation links | dynamic | ✅ or ❌ | +| `ps:tab-*` | These are the panel tab links | dynamic | ❌ | +| `ps:panel-*` | These are panel tab content areas | dynamic | ❌ | *(*) can be multiple elements - each incremented by an integer starting with `1`* + + ### Custom Event Hooks | **name** | **detail data** | **summary** | | --- | --- | --- | -| `ps:activeElementChanged` | `{ activeIndex: number, activeTab: DOMNode, activePanel: DOMNode }` | triggers when 'active-index' attribute is updated | +| `ps:tabchange` | `{ activeIndex: number, activeTab: DOMNode, activePanel: DOMNode }` | triggers when 'active' attribute is updated on any slot | ## Dependencies -| **name** | **location** | **type** | **reason** | **swappable** | +npne + ## Customization ### Hiding Slots -There might be times where you want to hide a slot but remove the default value. To do this you must add the slot as normal but add an additional `hidden` attribute to it. +There might be times where you want to hide a slot but remove the default value. To do this you must add the slot as normal but add an additional 'hidden' attribute to it. ```html @@ -67,10 +84,7 @@ There might be times where you want to hide a slot but remove the default value. ``` #### Current SEO Requirements -| **url** | **text** | -| --- | --- | -| | | - +none ## Base CSS Please note that some CSS is injected to the head of the document on initialization of this codebase (ONLY once). This is done to insure that some styling for slots does not get broken by any CSS reset the application. diff --git a/index.html b/index.html index 1a52c41..beed2e2 100644 --- a/index.html +++ b/index.html @@ -6,26 +6,29 @@ panel-set demo @@ -47,7 +50,7 @@

Lentil Soup

  • basil (2 tsp)
  • oregano (2 tbsp)
  • - Directions + Directions
    1. In a large pot, heat extra virgin olive oil over medium heat
    2. Stir in the onions, carrots, and celery
    3. @@ -100,5 +103,11 @@

      Lentil Soup

      ESHA Research, Inc. All Rights Reserved + \ No newline at end of file diff --git a/panel-set-helpers.mjs b/panel-set-helpers.mjs index 41aac81..b9413ae 100644 --- a/panel-set-helpers.mjs +++ b/panel-set-helpers.mjs @@ -6,143 +6,202 @@ export function render (component) { component.root.appendChild(document.importNode($template.content, true)); component.hasRendered = true; Array.from(component.querySelectorAll('[slot*="ps:panel"][active]'), (item, ind) => { - if(ind > 0) { item.removeAttribute('active'); } + if(ind > 0) { + item.removeAttribute('active'); + } else { + component.activeIndex = ind; + component.activePanel = item; + component.activeTab = component.querySelectorAll('[slot*="ps:tab"][active]')[ind] + } }); } } export function initialTemplate (component) { - return /* html */` - +
      + ${Array.from(tabSlots, (slot, ind) => { + if(!panelSlots[ind] || !tabSlots[ind]) { return ''; } + return /* html */` +
      + + ${!isTablet ? `` : ''} + + ${!isTablet ? `` : ''} +
      + `; + }).join('\n')} + +
      + ${isTablet ? renderSlots(component, '[slot^="ps:panel-"]') : ''} +
      +
      + `; + } +} - .accordion-summary { - width: 100%; - max-width: 100%; - display: flex; - } +export function css() { + return /* css */` + :host { + display: grid; + } - .accordion-details { - box-sizing: border-box; - width: 100%; - max-width: 100%; - overflow: auto; - border-left: solid 1px #bcbcbc; - border-right: solid 1px #bcbcbc; - padding: 0; - margin: 0; - } + .tab-slots, + .panel-slots { + display: none; + } + + .accordion-summary { + width: 100%; + max-width: 100%; + display: flex; + align-items: center; + color: var(--ps--summary-text-color, #fff); + background-color: var(--ps--summary-bkg-color, #bcbcbc); + } + + .accordion-details { + position: relative; + box-sizing: border-box; + width: 100%; + max-width: 100%; + overflow: auto; + border-left: solid 1px var(--ps--border-color, #bcbcbc); + border-right: solid 1px var(--ps--border-color, #bcbcbc); + padding: 0; + margin: 0; + } + + ::slotted([slot^='ps:tab']) { + display: block; + width: 100%; + padding: 8px; + text-decoration: none; + } + + ::slotted([slot^='ps:tab']:active), + ::slotted([slot^='ps:tab']:visited) { + color: var(--ps--summary-text-color, #fff); + } + + ::-webkit-details-marker { + border: none; + padding: 8px; + } + + ::slotted([slot^='ps:tab']:active), + ::slotted([slot^='ps:tab']:visited) { + color: var(--ps--tab-text-color, inherit); + } + + ::slotted([slot*='ps:panel']) { + height: 0px; + font-size: 0; + line-height: 0; + width: 0px; + text-indent: -9000px; + left: -100vw; + position: absolute; + } + + ::slotted([active]) { + height: unset; + font-size: unset; + line-height: unset; + width: 100%; + text-indent: unset; + left: unset; + position: static; + } + + ::slotted([slot^='ps:tab']) { + display: inline-block; + width: 75%; + position: relative; + z-index: 10; + top: 1px; + border: solid 1px var(--ps--border-color, #bcbcbc); + border-bottom-color: var(--ps--tab-border-color, rgba(255, 255, 255, 0)); + border-left: none; + } + @media screen and (min-width: 720px) { ::slotted([slot^='ps:tab']) { - display: block; - width: 100%; - padding: 8px; - border: solid 1px #bcbcbc; + display: unset; + width: auto; } - ::slotted([slot*='ps:panel']) { - height: 0px; - font-size: 0; - line-height: 0; - width: 0px; - text-indent: -9000px; - left: -100vw; - position: absolute; + ::slotted([slot^='ps:tab'][active]) { + border-bottom-color: var(--ps--tab-border-color--active, rgba(255, 255, 255, 1)); } - ::slotted([active]) { - height: unset; - font-size: unset; - line-height: unset; - width: 100%; - text-indent: unset; - left: unset; - position: static; + .tab-slots { + display: flex; + border-left: solid 1px var(--ps--border-color, #bcbcbc); } - - @media screen and (min-width: 720px) { - - .tab-slots { - display: flex; - border-left: solid 1px #bcbcbc; - } - - .panel-slots { - border: solid 1px #bcbcbc; - padding: 8px 16px; - } - - ::slotted([slot^='ps:tab']) { - width: auto; - position: relative; - z-index: 10; - top: 1px; - border-bottom-color: rgba(255, 255, 255, 0); - border-left: none; - } - ::slotted([slot^='ps:tab'][active]) { - border-bottom-color: rgba(255, 255, 255, 1); - } + .panel-slots { + display: block; + border: solid 1px var(--ps--border-color, #bcbcbc); + padding: 8px 16px; } - -
      - ${renderTemplates(component)} -
      - `; -} -export function renderTemplates(component) { - return ` - ${renderAccordionSlots(component)} - ${renderTabSlots(component)} + .accordion-section { + display: none; + } + } `; } -export function renderTabSlots(component) { - if (window.matchMedia('screen and (min-width: 720px)').matches) { - return ` - -
      - ${renderSlots(component, '[slot^="ps:panel-"]')} -
      - `; - } - return ''; -} - -export function renderAccordionSlots(component) { - if (!window.matchMedia('screen and (min-width: 720px)').matches) { - const tabSlots = component.querySelectorAll("[slot^='ps:tab']"); - const panelSlots = component.querySelectorAll("[slot^='ps:panel']"); - if (tabSlots && panelSlots && tabSlots.length === panelSlots.length) { - return [].map.call(tabSlots, (slot, ind) => { - return /* html */` -
      -
      - - -
      -
      - -
      -
      - `; - }).join('\n'); - } - } - return ''; -} - export function renderSlots(component, nodeListSelector) { const nodeList = component.querySelectorAll(nodeListSelector); return !!nodeList && Array.from(nodeList, (item) => { - return !!item ? /* html */` - - ` : ''; + return !!item ? /* html */`` : ''; }).join('\n'); +} + +export function eventControllers(component){ + const tabs = component.querySelectorAll("[slot^='ps:tab']"); + const details = component.root.querySelectorAll("details"); + const panels = component.querySelectorAll("[slot^='ps:panel']"); + return Array.from(tabs).map((el, indx) => { + const evtFn = (evt) => { + evt.preventDefault(); + if(panels && panels[indx]) { + Array.from(panels) + .concat(Array.from(tabs)) + .forEach((item, ind) => { + if(details[ind]) { details[ind].removeAttribute('open'); } + item.removeAttribute('active'); + }); + tabs[indx].setAttribute('active', 'true'); + panels[indx].setAttribute('active', 'true'); + details[indx].setAttribute('open', 'true'); + component.activeIndex = indx; + component.activePanel = panels[indx]; + component.activeTab = tabs[indx]; + component.dispatchEvent(new CustomEvent('ps:activeChanged', { detail: { + activeIndex: indx, + activePanel: panels[indx], + activeTab: tabs[indx] + } + })); + } + }; + tabs[indx].addEventListener('click', evtFn, false); + }); +} + +export async function initialize(component) { + await render(component); + eventControllers(component); } \ No newline at end of file diff --git a/panel-set.mjs b/panel-set.mjs index 5534210..16766a5 100644 --- a/panel-set.mjs +++ b/panel-set.mjs @@ -1,45 +1,29 @@ (async function(window, document){ if(typeof window === 'undefined'){ return; } - const { render, renderTemplates } = await import('./panel-set-helpers.mjs'); + const { initialize } = await import('./panel-set-helpers.mjs'); const customElementName = 'panel-set'; if (!window.customElements.get(customElementName)) { window.customElements.define(customElementName, class PanelSet extends HTMLElement { static get observedAttributes() { - return [ 'max-panels', 'active-panel' ]; + return []; } constructor(){ super(); this.root = null; + this.activeIndex = null; + this.activePanel = null; + this.activeTab = null; this.hasRendered = false; - this.maxPanels = 6; - this.activePanel = 1; - } - - attributeChangedCallback(name, oldVal, newVal){ - if (!newVal) { return } - const int = parseInt(newVal, 10); - const isIntegerAndGreaterThanZero = typeof int === 'number' && !isNaN(int) && int > 0; - if (name === 'max-panels') { - this.maxPanels = isIntegerAndGreaterThanZero ? int : this.maxPanels; - } - if (name === 'active-panel') { - this.activePanel = isIntegerAndGreaterThanZero ? int : this.activePanel; - } - if(this.root){ - this.root.querySelector('#template-container').innerHTML = renderTemplates(this); - } } connectedCallback(){ if(!this.root) { this.root = this.attachShadow({ mode: 'open' }); } - window.addEventListener('resize', ()=> render(this)); - render(this); - } - - disconnectedCallback(){ + initialize(this); + this.hasRendered = true; + window.addEventListener('resize', () => initialize(this)); } } );