deck-presenter 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,83 @@
1
+ # deck-presenter
2
+
3
+ A presenter window for a [`<deck-shift>`](../deck-shift) slide deck.
4
+
5
+ ```html
6
+ <script type="module" src="https://unpkg.com/deck-presenter"></script>
7
+ <deck-presenter></deck-presenter>
8
+ ```
9
+
10
+ Or:
11
+
12
+ ```sh
13
+ npm install deck-presenter
14
+ ```
15
+
16
+ ```js
17
+ import "deck-presenter";
18
+ ```
19
+
20
+ Add `<deck-presenter>` anywhere on the page and press <kbd>s</kbd>, and a new
21
+ tab will open with a timer (click to reset), the position, back and next, the
22
+ clock, the current slide, and the next, with notes below.
23
+
24
+ The popup is built and run from the deck's own window, so its buttons and keys
25
+ drive the main slide deck.
26
+
27
+ ### Customizing
28
+
29
+ Add a `<template>` child to the `<deck-presenter>` to inject arbitrary HTML to
30
+ the opened window, this can include CSS, scripts, or even custom elements which
31
+ will all be rendered into the opened window, allowing you to customise the
32
+ presenter view.
33
+
34
+ Scripts run in that window, with URLs relative to the deck. Module scripts run
35
+ once per window.
36
+
37
+ ```html
38
+ <deck-presenter>
39
+ <template>
40
+ <script type="module" src="https://esm.sh/webcam-view"></script>
41
+ <style>
42
+ webcam-view {
43
+ position: fixed;
44
+ right: 24px;
45
+ bottom: 24px;
46
+ width: 280px;
47
+ }
48
+ </style>
49
+ <webcam-view mirror></webcam-view>
50
+ </template>
51
+ </deck-presenter>
52
+ ```
53
+
54
+ The view lives in a shadow root, so style it from the template with
55
+ `::part()`:
56
+
57
+ - `header`, holding `timer`, `position`, `prev-button`, `next-button` (both
58
+ also `button`) and `clock`.
59
+ - `main`, holding the `current-slide` frame and the `sidebar`, which holds the
60
+ `label`, the `next-slide` frame (both frames are also `frame`), `end` (shown
61
+ over the next frame on the last step) and `notes`.
62
+ - `cue` for each `<mark>` in the notes, plus `cue-hit` once passed or
63
+ `cue-next` for the one the next press reaches.
64
+ - `help`, the shortcuts dialog.
65
+
66
+ ```html
67
+ <deck-presenter>
68
+ <template>
69
+ <style>
70
+ ::part(main) {
71
+ grid-template-columns: 1fr 1fr;
72
+ }
73
+ ::part(cue-next) {
74
+ background: hotpink;
75
+ }
76
+ </style>
77
+ </template>
78
+ </deck-presenter>
79
+ ```
80
+
81
+ Notes are the slide's `<aside class="notes">`, with a `<mark>` at the word where
82
+ you press for each step. It adds `--present` and S to the deck with
83
+ `addShortcut()`.
@@ -0,0 +1,2 @@
1
+ var S=`:host{position:fixed;inset:0;display:grid;grid-template-rows:auto 1fr;background:#111;color:#eee;font:16px/1.45 ui-sans-serif,system-ui,sans-serif}header{display:flex;gap:28px;align-items:baseline;padding:14px 22px;border-bottom:1px solid #2a2a2a}[part=timer]{font:600 34px/1 ui-monospace,monospace;cursor:pointer;font-variant-numeric:tabular-nums}[part=position]{flex:1;color:#aaa}button{background:#262626;color:#eee;border:1px solid #3a3a3a;border-radius:8px;padding:9px 16px;cursor:pointer;font:600 15px/1 ui-sans-serif,system-ui,sans-serif;&:hover{background:#333}}[part=clock]{font:500 22px/1 ui-monospace,monospace;color:#aaa}main{display:grid;grid-template-columns:1.55fr 1fr;gap:22px;padding:22px;min-height:0}aside{display:grid;grid-template-rows:auto 1fr;gap:18px;min-height:0}label{display:block;color:#888;font-size:13px;text-transform:uppercase;letter-spacing:.12em;margin-bottom:6px}[part=frame]{position:relative;aspect-ratio:16 / 9;background:#000;border-radius:6px;overflow:hidden;iframe{position:absolute;inset:0;width:100%;height:100%;border:0;pointer-events:none}.end{position:absolute;inset:0;display:none;place-items:center;color:#777;font-size:24px;background:#000}&.over .end{display:grid}}[part=notes]{overflow:auto;font-size:24px;line-height:1.45;color:#f2f2f2;padding-right:8px;p{margin:0 0 .7em}em.none{color:#777}mark{background:none;color:inherit;border-bottom:2px solid #555}mark[part~=cue-hit]{border-color:#6c6;color:#9d9}mark[part~=cue-next]{background:var(--deck-ui-accent, #ffd23f);color:#000;border-radius:3px;border-color:transparent}}dialog{border:0;padding:24px 30px 18px;border-radius:12px;background:#fff;color:#111;box-shadow:0 20px 60px #0000004d;font:15px/1.5 ui-sans-serif,system-ui,sans-serif;&::backdrop{background:#0000008c}h1{margin:0 0 12px;font-size:18px}td{padding:3px 14px 3px 0}td:first-child{color:#555;white-space:nowrap}kbd{display:inline-block;min-width:1.4em;padding:0 .35em;border:1px solid rgba(127,127,127,.45);border-bottom-width:2px;border-radius:4px;font:600 .85em/1.5 ui-monospace,monospace;text-align:center}}
2
+ ;`;var L='<header part=header><span part=timer title="click to reset">00:00</span><span part=position></span> <button part="button prev-button" aria-label=Back>&lsaquo; Back</button><button part="button next-button" aria-label=Next>Next &rsaquo;</button> <span part=clock></span></header><main part=main><section><div part="frame current-slide"><iframe tabindex=-1></iframe></div></section><aside part=sidebar><div><label part=label>Next</label><div class="frame next" part="frame next-slide"><iframe tabindex=-1></iframe><div class=end part=end>End of deck</div></div></div><div part=notes></div></aside></main><dialog closedby=any part=help aria-label="Keyboard shortcuts"><h1>Presenter view</h1><table><tr><td><kbd>&rarr;</kbd> <kbd>Space</kbd> <kbd>PgDn</kbd><td>next step<tr><td><kbd>&larr;</kbd> <kbd>PgUp</kbd><td>back<tr><td><kbd>&darr;</kbd> <kbd>&uarr;</kbd><td>next / previous slide<tr><td><kbd>Home</kbd> <kbd>End</kbd><td>first / last slide<tr><td>click the timer<td>reset it<tr><td><kbd>?</kbd><td>this help</table></dialog>;';var k=class extends HTMLElement{static define(r="deck-presenter",o=customElements){o.define(r,this)}#e=null;#t=null;async connectedCallback(){if(this.#e)return;let r=this.closest("deck-shift")??this.ownerDocument.querySelector("deck-shift");await customElements.whenDefined(r.localName),this.#e=r,this.addEventListener("command",o=>o.command==="--present"&&this.open()),this.#e.addShortcut("--present","s",()=>this.open(),"Open presenter mode")}open(){let r=open("","deck-presenter","popup,width=1400,height=860");if(!r||!this.#e)return;this.#t?.abort();let{signal:o}=this.#t=new AbortController,n=this.#e,a=r.document;a.title=`Presenter: ${document.title}`,a.body.replaceChildren(),a.body.style.margin="0";let x=a.body.appendChild(a.createElement("div"));x.style.setProperty("--deck-ui-accent",getComputedStyle(this).getPropertyValue("--deck-ui-accent"));let g=this.querySelector(":scope > template");g&&a.body.append(a.importNode(g.content,!0));let l=x.attachShadow({mode:"open"});l.innerHTML=`<style>${S}</style>`+L;let i=e=>l.querySelector(`[part~=${e}]`),[b,s]=l.querySelectorAll("iframe"),y=i("timer"),C=i("position"),K=i("clock"),d=i("notes"),u=i("help");i("prev-button").onclick=()=>n.prev(),i("next-button").onclick=()=>n.next(),r.addEventListener("keydown",e=>{if(e.key==="?")return u.open?u.close():u.showModal();if(/^[sf]$/i.test(e.key)||e.target.isContentEditable||/^(INPUT|TEXTAREA|SELECT)$/.test(e.target.tagName))return;let t=new KeyboardEvent("keydown",{key:e.key,code:e.code,shiftKey:e.shiftKey,altKey:e.altKey,ctrlKey:e.ctrlKey,metaKey:e.metaKey,cancelable:!0});dispatchEvent(t)||e.preventDefault()},{signal:o});let v=Date.now();y.onclick=()=>v=Date.now();let c=e=>String(e).padStart(2,"0"),N=setInterval(()=>{let e=Math.floor((Date.now()-v)/1e3),t=new Date;y.textContent=`${c(Math.floor(e/60))}:${c(e%60)}`,K.textContent=`${c(t.getHours())}:${c(t.getMinutes())}`},250);o.addEventListener("abort",()=>clearInterval(N));let w=(e,t)=>e.contentWindow?.postMessage({deck:"goto",...t},"*"),m=()=>{let{slide:e,step:t,slides:h}=n,q=h[e],M=n.script()[e].title,f=n.steps,p=t<f?[e,t+1]:e+1<h.length?[e+1,0]:null;C.textContent=`${e+1} / ${h.length}${f?` \xB7 step ${t+1} of ${f+1}`:""} \xB7 ${M}`,w(b,{slide:e,step:t}),s.parentElement.classList.toggle("over",!p),p&&w(s,{slide:p[0],step:p[1],animate:!1});let E=q.querySelector(":scope > aside.notes");if(!E)return void(d.innerHTML='<em class="none">No notes for this slide.</em>');d.replaceChildren(...a.importNode(E,!0).childNodes),d.querySelectorAll("mark").forEach((T,$)=>T.setAttribute("part",$<t?"cue cue-hit":$===t?"cue cue-next":"cue")),d.querySelector('mark[part~="cue-next"]')?.scrollIntoView({block:"center",behavior:"smooth"})};n.addEventListener("deckchange",m,{signal:o});for(let e of[b,s])e.addEventListener("load",m,{signal:o});r.addEventListener("pagehide",()=>this.#t.signal===o&&this.#t.abort(),{signal:o}),b.src=s.src=`${location.href.split("#")[0]}#${n.slide+1}.${n.step}`,m()}};new URL(import.meta.url).searchParams.has("nodefine")||k.define();export{k as DeckPresenter};
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "deck-presenter",
3
+ "version": "0.1.0",
4
+ "description": "A presenter window for a <deck-shift> slide deck: timer, current and next slide, and notes with cues.",
5
+ "keywords": [
6
+ "web-components",
7
+ "custom-elements",
8
+ "slides",
9
+ "presentation",
10
+ "speaker-notes"
11
+ ],
12
+ "author": "Keith Cirkel",
13
+ "type": "module",
14
+ "exports": "./deck-presenter.min.js",
15
+ "files": [
16
+ "deck-presenter.min.js"
17
+ ],
18
+ "scripts": {
19
+ "build": "node esbuild.js"
20
+ },
21
+ "peerDependencies": {
22
+ "deck-shift": "^0.1.0"
23
+ }
24
+ }