Last active
April 26, 2019 01:56
-
-
Save JanMiksovsky/79a4868e48f554e3a147c578e97e6d42 to your computer and use it in GitHub Desktop.
Hypothetical styleable carousel component showing custom parts and custom pseudo-classes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<html> | |
<head> | |
<title>Styling a part in combination with a custom pseudo-class</title> | |
<script type="module"> | |
// This code sample shows a hypothetical styleable carousel component | |
// modeled after https://component.kitchen/elix/Carousel. | |
// | |
// This shows two related web component features which are desired: | |
// | |
// 1. The carousel element generates a set of dots and exposes those as parts that | |
// can be styled from the outside via `::part(dot)`. | |
// 2. The separate dot element uses element internals to expose a custom | |
// pseudo-class called `:selected` that's true if a dot represents the | |
// item currently selected in the carousel. | |
// | |
// These two features can be used in combination to style a carousel's | |
// dots, taking into account each dot's selected state. | |
// | |
// The relevant lines of code are marked with *NOTE*. | |
// A single dot used by the carousel | |
class DotElement extends HTMLElement { | |
// *NOTE* Create element internals so we can set pseudo-classes. | |
#internals = this.attachInternals(); | |
#selected = false; | |
// Not shown: Creation and population of shadow root... | |
// Expose a property the carousel can set to true if the dot represents | |
// the selected item in the carousel. | |
get selected() { | |
return this.#selected; | |
} | |
set selected(selected) { | |
this.#selected = selected; | |
// *NOTE* Reflect selected state as `:selected` pseudo-class. | |
this.#internals.pseudoClasses.toggle('selected', selected); | |
} | |
} | |
customElements.define('dot-element', DotElement); | |
// Simplistic carousel. | |
class CarouselElement extends HTMLElement { | |
#dotsContainer; | |
#defaultSlot; | |
#selectedIndex = -1; | |
constructor() { | |
this.attachShadow({ mode: 'open' }); | |
this.shadowRoot.innerHTML = ` | |
<style> | |
/* Default styling */ | |
#dot-element { | |
background: gray; | |
} | |
#dot-element:selected { | |
background: white; | |
} | |
</style> | |
<div id="dotsContainer"></div> | |
<slot></slot> | |
`; | |
// Generate new dots when the items change. | |
this.#defaultSlot = this.shadowRoot.querySelector('slot'); | |
this.#defaultSlot.addEventListener('slotchange', () => { | |
this.generateDots(); | |
}); | |
// If the user clicks a dot, make that dot selected. | |
this.#dotsContainer = this.shadowRoot.querySelector('dotsContainer'); | |
this.#dotsContainer.addEventListener('click', event => { | |
this.selectedIndex = this.dots.indexOf(event.target); | |
}); | |
} | |
// Return the current set of dots. | |
get dots() { | |
return #dotsContainer.children; | |
} | |
// Generate dot parts for the given set of items. | |
generateDots() { | |
this.#dotsContainer.innerHTML = ''; // Erase existing dots | |
this.items.forEach(item => { | |
// Create a dot. | |
const dot = new DotElement(); | |
// *NOTE* Expose each new dot as a part called `dot`. | |
dot.setAttribute('part', 'dot'); | |
this.#dotsContainer.appendChild(dot); | |
}); | |
} | |
// Return the set of items (e.g., images) being shown in the carousel. | |
get items() { | |
return this.#defaultSlot.assignedNodes({ flatten: true }); | |
} | |
// Get/set the index of the item selected in the carousel. | |
get selectedIndex() { | |
return this.#selectedIndex; | |
} | |
set selectedIndex(newIndex) { | |
// *NOTE* When the carousel sets the `selected` property on dots, that | |
// will update the dot's `:selected` pseudo-class. An external host | |
// like the carousel can't directly set pseudo-classes. | |
const oldIndex = this.#selectedIndex; | |
if (oldIndex >= 0) { | |
// Unselect old dot. | |
this.dots[oldIndex].selected = false; | |
} | |
this.#selectedIndex = newIndex; | |
// Select new dot. | |
this.dots[newIndex].selected = true; | |
// Not shown: Arrange to actually display the corresponding item... | |
} | |
} | |
customElements.define('carousel-element', CarouselElement); | |
</script> | |
<style> | |
/* *NOTE* custom styling to make dots pink or (when selected) red. */ | |
carousel-element::part(dot) { | |
background: pink; | |
} | |
carousel-element::part(dot):selected { | |
background: red; | |
} | |
</style> | |
</head> | |
<body> | |
<carousel-element> | |
<img src="image1.jpg"> | |
<img src="image2.jpg"> | |
<img src="image3.jpg"> | |
</carousel-element> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment