Skip to content

Instantly share code, notes, and snippets.

@JanMiksovsky
Last active April 26, 2019 01:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JanMiksovsky/79a4868e48f554e3a147c578e97e6d42 to your computer and use it in GitHub Desktop.
Save JanMiksovsky/79a4868e48f554e3a147c578e97e6d42 to your computer and use it in GitHub Desktop.
Hypothetical styleable carousel component showing custom parts and custom pseudo-classes
<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