Skip to content

Commit

Permalink
Merge pull request #10 from oddbird/controls
Browse files Browse the repository at this point in the history
Control panel styles and parts
  • Loading branch information
mirisuzanne authored Jan 4, 2024
2 parents b87385a + 5f74602 commit 23afeca
Show file tree
Hide file tree
Showing 5 changed files with 183 additions and 119 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@ until we achieve a stable v1.0 release

## v0.1.2 - unreleased

- 💥 BREAKING: Removed the shadow DOM content wrapper,
and all shadow DOM styles
- 💥 BREAKING: Renamed and added control-panel parts,
to allow for more customization of the default panel
- 🚀 NEW: Default styles are in `slide-deck.css`
and can be applied from the light DOM
- 🚀 NEW: The entire control panel can be replaced
from the light DOM using `slot=control-panel`
on a slotted `dialog` element
- 🐞 FIXED: Slotted controls are no longer treated as slides
- 🚀 NEW: When `key-control` is activated (including on-load),
we target the stored active slide (or the first slide)
- 🐞 FIXED: When restoring the active slide from memory,
Expand Down
37 changes: 37 additions & 0 deletions custom-panel.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="en" style="color-scheme: light dark">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="Demo of slide-deck Web Component" />
<title>Slide-Deck Web Component Demo</title>
<link rel="stylesheet" href="slide-deck.css">
<style>body { margin: 0; }</style>
<script type="module" src="slide-deck.js"></script>
</head>
<body>
<slide-deck id="my-slides" key-control>
<dialog slot="control-panel">
<div>
<button set-view>grid</button>
<button set-view>list</button>
<form method="dialog"><button>close</button></form>
</div>
</dialog>
<header>
<h1>Slide-Deck Web Component</h1>
<p>
<a href="https://github.com/oddbird/slide-deck/">github.com/oddbird/slide-deck/</a>
</p>
</header>
<div>
<h2>Custom Control Panel</h2>
<p><strong>command-k</strong> to open</p>
</div>
<div>
<a href="./index.html">Back to demo</a>
</div>
</slide-deck>

</body>
</html>
9 changes: 7 additions & 2 deletions index.html
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<html lang="en" style="color-scheme: light dark">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="Demo of slide-deck Web Component" />
<title>Slide-Deck Web Component Demo</title>

<link rel="stylesheet" href="slide-deck.css">
<style>body { margin: 0; }</style>
<script type="module" src="slide-deck.js"></script>
</head>
<body>
Expand Down Expand Up @@ -92,6 +93,10 @@ <h2><code>&lt;button set-view&gt;list&lt;button&gt;</code></h2>
<button set-view>grid</button>
</div>
</div>
<div>
<h2>Custom Control Panel</h2>
<a href="./custom-panel.html">Example</a>
</div>
<div><h2>Speaker View</h2></div>
<div>
<h2>Open Source</h2>
Expand Down
73 changes: 73 additions & 0 deletions slide-deck.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
slide-deck {
--gap: clamp(8px, 0.25em + 1vw, 16px);
container: slide-deck / inline-size;
display: grid;

&[slide-view=grid] {
--slide-ratio: 16/9;
--target-outline: medium dotted;
--target-margin: var(--gap);
gap: var(--gap);
grid-template-columns: repeat(auto-fill, minmax(min(50ch, 100%), 1fr));
padding: var(--gap);
}

&[slide-view=list] {
--slide-height: 100svh;
}

&[blank-slide]::after {
content: '';
background-color: var(--blank-color, black);
position: absolute;
inset: 0;
}

&[blank-slide=white] {
--blank-color: white;
}

[id^=slide_] {
aspect-ratio: var(--slide-ratio);
border: thin solid;
box-sizing: border-box;
container: slide-item / inline-size;
min-height: var(--slide-height);
padding: var(--gap);
scroll-margin-block: var(--target-margin);

&:target {
outline: var(--target-outline);
outline-offset: calc(var(--gap) * 0.5);
}
}

&::part(control-panel) {
min-width: min(50ch, 100%);
padding: 0;
}

&::part(panel-header) {
border-block-end: thin solid;
display: grid;
gap: var(--gap);
grid-template-columns: 1fr auto;
padding: var(--gap);
}

&::part(controls) {
padding: var(--gap);
}

button,
&::part(button) {
font: inherit;
padding-inline: var(--gap);
border: medium solid transparent;
}

[aria-pressed=true],
&::part(button pressed) {
border-color: currentColor;
}
}
173 changes: 56 additions & 117 deletions slide-deck.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,122 +3,44 @@ class slideDeck extends HTMLElement {
static appendShadowTemplate = (node) => {
const template = document.createElement("template");
template.innerHTML = `
<dialog part="controls">
<form method="dialog"><button>close</button></form>
<div>
<slot name="slide-controls">
<button slide-event='toggleControl'>keyboard controls</button>
<slot></slot>
<slot name="control-panel">
<dialog part="control-panel">
<div part="panel-header">
<strong>Slide-Deck Controls</strong>
<form method="dialog"><button>close</button></form>
</div>
<div part="controls">
<button part="button" slide-event='toggleControl'>
keyboard navigation
</button>
<hr>
<p><strong>Presentation:</strong></p>
<button slide-event>start</button>
<button slide-event>end</button>
<button slide-event="joinWithNotes">speaker view</button>
<button part="button event" slide-event>
start
</button>
<button part="button event" slide-event>
end
</button>
<button part="button event" slide-event="joinWithNotes">
speaker view
</button>
<hr>
<p><strong>View:</strong></p>
<button set-view>grid</button>
<button set-view>list</button>
</slot>
</div>
</dialog>
<div part="contents">
<slot></slot>
</div>
<button part="button view" set-view>grid</button>
<button part="button view" set-view>list</button>
</div>
</dialog>
</slot>
`;
const shadowRoot = node.attachShadow({ mode: "open" });
shadowRoot.appendChild(template.content.cloneNode(true));
}

// css
static adoptShadowStyles = (node) => {
const shadowStyle = new CSSStyleSheet();
shadowStyle.replaceSync(`
:host {
position: relative;
}
:host:not(:fullscreen) {
container: host / inline-size;
}
:host(:fullscreen) {
background-color: white;
overflow-x: clip;
overflow-y: auto;
}
:host([slide-view=grid]) {
---slide-grid-ratio: 16/9;
---slide-grid-border: var(--slide-grid-border, thin solid);
---slide-grid-active-outline: medium dotted hotpink;
---slide-grid-scroll-margin: clamp(10px, 4cqi, 40px);
---slide-list-border: var(--slide-grid-border, thin solid);
}
:host([slide-view=list]) {
---slide-list-border: var(--slide-list-border, thin solid);
}
:host([blank-slide])::after {
content: '';
position: absolute;
inset: 0;
background-color: var(--blank-slide-color, black);
}
:host([blank-slide='white'])::after {
--blank-slide-color: white;
}
[part=contents] {
---slide-gap: clamp(5px, 1.5cqi, 15px);
display: grid;
:host([slide-view=grid]) & {
grid-template-columns: var(
--slide-grid-columns,
repeat(auto-fit, minmax(min(50ch, 100%), 1fr))
);
gap: var(--slide-grid-gap, var(---slide-gap));
padding: var(--slide-grid-padding, var(---slide-gap));
}
:host([slide-view=list]) & {
grid-auto-rows: var(--slide-list-rows, 100svh);
}
}
::slotted([id^=slide_]) {
aspect-ratio: var(--slide-grid-ratio, var(---slide-grid-ratio));
container-name: slide;
container-type: var(--slide-container, inline-size);
border: var(---slide-grid-border);
border-block-end: var(---slide-list-border);
padding: var(---slide-gap);
scroll-margin: var(
--slide-grid-scroll-margin,
var(---slide-grid-scroll-margin)
);
}
::slotted([id^=slide_]:target) {
outline: var(
--slide-grid-active-outline,
var(---slide-grid-active-outline)
);
outline-offset: var(--slide-active-outline-offset, 3px);
}
button[aria-pressed=true] {
box-shadow: inset 0 0 2px black;
&::before {
content: ' ✅ ';
}
}
`);
node.shadowRoot.adoptedStyleSheets = [shadowStyle];
}

// static
static observedAttributes = [
'key-control',
Expand Down Expand Up @@ -210,17 +132,18 @@ class slideDeck extends HTMLElement {

// shadow dom and ID
slideDeck.appendShadowTemplate(this);
slideDeck.adoptShadowStyles(this);
this.setDeckID();

// relevant nodes
this.body = document.querySelector('body');
this.controlPanel = this.shadowRoot.querySelector(`[part="controls"]`);
this.controlPanel = this.querySelector(`[slot="control-panel"]`) ??
this.shadowRoot.querySelector(`[part="control-panel"]`);

// initial setup
this.slideCount = this.childElementCount;
const slides = this.querySelectorAll(':scope > :not([slot])');
this.slideCount = slides.length;
this.defaultAttrs();
this.setSlideIDs();
this.setSlideIDs(slides);
this.goTo();

// buttons
Expand Down Expand Up @@ -289,9 +212,7 @@ class slideDeck extends HTMLElement {

slideId = (n) => `slide_${this.id}-${n}`;

setSlideIDs = () => {
const slides = this.querySelectorAll(':scope > *');

setSlideIDs = (slides) => {
slides.forEach((slide, index) => {
slide.id = this.slideId(index + 1);
});
Expand All @@ -309,6 +230,24 @@ class slideDeck extends HTMLElement {

// buttons
getButtonEvent = (btn) => btn.getAttribute('slide-event') || btn.innerText;
setPressed = (btn, isPressed) => {
btn.setAttribute('aria-pressed', isPressed);

if (btn.hasAttribute('part')) {
const currentNames = btn.getAttribute('part').split(' ');
let newNames;

if (isPressed) {
newNames = currentNames.includes('pressed')
? currentNames
: [...currentNames, 'pressed'];
} else if (!isPressed) {
newNames = currentNames.filter((name) => name !== 'pressed')
}

btn.setAttribute('part', newNames.join(' '));
}
}

updateEventButtons = () => {
this.eventButtons.forEach((btn) => {
Expand All @@ -320,7 +259,7 @@ class slideDeck extends HTMLElement {
}

if (Object.keys(isActive).includes(btnEvent)) {
btn.setAttribute('aria-pressed', isActive[btnEvent]);
this.setPressed(btn, isActive[btnEvent]);
}
});
}
Expand Down Expand Up @@ -362,7 +301,7 @@ class slideDeck extends HTMLElement {
updateViewButtons = () => {
this.viewButtons.forEach((btn) => {
const isActive = this.getButtonView(btn) === this.slideView;
btn.setAttribute('aria-pressed', isActive);
this.setPressed(btn, isActive);
});
}

Expand Down

0 comments on commit 23afeca

Please sign in to comment.