diff --git a/CHANGELOG.md b/CHANGELOG.md
index f88b317..6fb6577 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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,
diff --git a/custom-panel.html b/custom-panel.html
new file mode 100644
index 0000000..604b1a0
--- /dev/null
+++ b/custom-panel.html
@@ -0,0 +1,37 @@
+
+
+
+
Open Source
diff --git a/slide-deck.css b/slide-deck.css
new file mode 100644
index 0000000..3c2c2da
--- /dev/null
+++ b/slide-deck.css
@@ -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;
+ }
+}
diff --git a/slide-deck.js b/slide-deck.js
index 17cb295..5681a82 100644
--- a/slide-deck.js
+++ b/slide-deck.js
@@ -3,122 +3,44 @@ class slideDeck extends HTMLElement {
static appendShadowTemplate = (node) => {
const template = document.createElement("template");
template.innerHTML = `
-
-
-
-
- keyboard controls
-
+
+
+
+
+ Slide-Deck Controls
+
+
+
+
+ keyboard navigation
+
+
+
Presentation:
-
start
-
end
-
speaker view
+
+ start
+
+
+ end
+
+
+ speaker view
+
+
+
View:
-
grid
-
list
-
-
-
-
-
-
+
+ grid
+ list
+
+
+
`;
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',
@@ -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
@@ -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);
});
@@ -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) => {
@@ -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]);
}
});
}
@@ -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);
});
}