deck-controls 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,29 @@
1
+ # deck-controls
2
+
3
+ On-screen playback controls for a [`<deck-shift>`](../deck-shift) slide deck.
4
+
5
+ ```html
6
+ <script type="module" src="https://unpkg.com/deck-controls@1.0.0"></script>
7
+ <deck-controls></deck-controls>
8
+ ```
9
+
10
+ Or:
11
+
12
+ ```sh
13
+ npm install deck-controls
14
+ ```
15
+
16
+ ```js
17
+ import "deck-controls";
18
+ ```
19
+
20
+ Add `<deck-controls>` for on-screen controls: a compact floating toolbar will
21
+ show on mouse move with a scrubber (a segment per slide), and icon buttons for
22
+ the presenter view, recording (with pause while it runs), full screen and help.
23
+ It shows while the mouse moves and hides, with the cursor.
24
+
25
+ The CSS custom properties `--deck-ui-bg`, `--deck-ui-fg`, `--deck-ui-accent` and
26
+ `--deck-ui-track` can be used to style it. Or select `::part(bar)`.
27
+
28
+ It stays out of sight until mouse movement in the window, so a deck in an iframe
29
+ never shows it.
@@ -0,0 +1,2 @@
1
+ var c=new CSSStyleSheet;c.replaceSync(`[hidden]{display:none!important}:host{position:fixed;inset:0;z-index:2147483000;pointer-events:none;view-transition-name:deck-controls;font:500 14px/1 ui-sans-serif,system-ui,sans-serif;color:var(--deck-ui-fg, #f4f4f4)}[part=bar]{position:absolute;left:50%;top:16px;display:flex;align-items:center;gap:2px;padding:4px;border-radius:12px;background:var(--deck-ui-bg, rgba(22, 22, 24, .88));box-shadow:0 8px 24px #00000040;backdrop-filter:blur(8px);translate:-50% 10px;opacity:0;transition:opacity .25s,translate .25s}:host(:state(on)) [part=bar]{opacity:1;translate:-50% 0;pointer-events:auto}.scrub{display:flex;gap:3px;width:clamp(120px,24vw,280px);height:30px;align-items:center;padding:0 8px}.seg{flex:1;height:30px;padding:0;border:0;cursor:pointer;position:relative;&:before,i{content:"";position:absolute;left:0;right:0;top:13px;height:4px;border-radius:2px}&:before{background:var(--deck-ui-track, rgba(255, 255, 255, .28))}&:hover{background:none;&:before{background:#fff9}}i{background:var(--deck-ui-accent, #f4f4f4);transform-origin:0 50%;scale:0 1}}button{position:relative;width:30px;height:30px;flex:none;padding:0;border:0;border-radius:8px;background:none;color:inherit;cursor:pointer;display:grid;place-items:center;&:hover{background:#ffffff1f}&:disabled{opacity:.3;cursor:default;background:none}}[command=--record]{[name=stop]{display:none}:host(:state(recording)) &{[name=record]{display:none}[name=stop]{display:revert}}}[command=--record-pause]{[name=pause]{display:none}:host(:not(:state(recording))) &{display:none}:host(:not(:state(recording-paused))) &{[name=resume]{display:none}[name=pause]{display:revert}}}[command=--fullscreen]{[name=minimise]{display:none}:host(:state(fullscreen)) &{[name=minimise]{display:revert}[name=maximise]{display:none}}}svg{width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}.sep{width:1px;height:18px;background:#fff3;margin:0 2px}[data-tip]:hover:after{content:attr(data-tip);position:absolute;top:40px;left:50%;translate:-50% 0;white-space:nowrap;padding:6px 9px;border-radius:7px;background:#111;color:#fff;font:500 13px/1 ui-sans-serif,system-ui,sans-serif}.seg[data-tip]:hover:after{bottom:30px}[part=edge]{position:absolute;top:50%;width:44px;height:44px;margin-top:-22px;border-radius:50%;display:grid;place-items:center;background:var(--deck-ui-bg, rgba(22, 22, 24, .7));opacity:0;transition:opacity .2s;svg{width:24px;height:24px}&.l{left:18px}&.r{right:18px}}:host(:state(edge-l)) [part=edge].l,:host(:state(edge-r)) [part=edge].r{opacity:.8}:host(:state(idle)) :is([part=bar],[part=edge]){opacity:0!important;pointer-events:none}@media(prefers-reduced-motion:reduce){[part=bar],.edge{transition:none}}
2
+ `);var h=c;var p='<div part=bar><div class=scrub role=navigation aria-label=Slides></div><i class=sep></i> <button command=--present hidden data-tip="Presenter view (S)" aria-label="Presenter View"><svg viewBox="0 0 24 24" aria-hidden=true><path d="M2 3h20"/><path d="M21 3v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V3"/><path d="m7 21 5-5 5 5"/></svg></button> <button command=--record hidden data-tip="Record (R)" aria-label=Record><svg viewBox="0 0 24 24" aria-hidden=true name=record><circle cx=12 cy=12 r=6 fill=#e5484d /></svg> <svg viewBox="0 0 24 24" aria-hidden=true name=stop><rect x=6 y=6 width=12 height=12 rx=2 fill=#e5484d /></svg></button> <button command=--record-pause data-attr="Pause recording (.)" aria-label="Pause recording"><svg viewBox="0 0 24 24" aria-hidden=true name=pause><rect x=14 y=4 width=4 height=16 rx=1 /><rect x=6 y=4 width=4 height=16 rx=1 /></svg> <svg viewBox="0 0 24 24" aria-hidden=true name=resume><polygon points="6 3 20 12 6 21 6 3"/></svg></button> <button command=--fullscreen data-tip="Full screen (F)"><svg viewBox="0 0 24 24" aria-hidden=true name=maximise><path d="M8 3H5a2 2 0 0 0-2 2v3"/><path d="M21 8V5a2 2 0 0 0-2-2h-3"/><path d="M3 16v3a2 2 0 0 0 2 2h3"/><path d="M16 21h3a2 2 0 0 0 2-2v-3"/></svg> <svg viewBox="0 0 24 24" aria-hidden=true name=minimise><path d="M8 3v3a2 2 0 0 1-2 2H3"/><path d="M21 8h-3a2 2 0 0 1-2-2V3"/><path d="M3 16h3a2 2 0 0 1 2 2v3"/><path d="M16 21v-3a2 2 0 0 1 2-2h3"/></svg></button> <button command=--help data-tip="Shortcuts (?)" aria-label=Shortcuts><svg viewBox="0 0 24 24" aria-hidden=true name=minimise><path d="M10 8h.01"/><path d="M12 12h.01"/><path d="M14 8h.01"/><path d="M16 12h.01"/><path d="M18 8h.01"/><path d="M6 8h.01"/><path d="M7 16h10"/><path d="M8 12h.01"/><rect width=20 height=16 x=2 y=4 rx=2 /></svg></button><slot></slot></div><div part=edge class=l><svg viewBox="0 0 24 24" aria-hidden=true><path d="m15 18-6-6 6-6"/></svg></div><div part=edge class=r><svg viewBox="0 0 24 24" aria-hidden=true><path d="m9 18 6-6-6-6"/></svg></div>;';var l=2600,u=.2,o=new CSSStyleSheet,x="@layer deck-controls { * { cursor: none !important; } }",m=r=>r.replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;"),g=(r,e,s)=>{r.dataset.tip=`${e} (${s})`,r.ariaLabel=e},n=class extends HTMLElement{static define(e="deck-controls",s=customElements){s.define(e,this)}#a=!1;#r=0;#i=0;#e=this.attachInternals();shadowRoot=Object.assign(this.attachShadow({mode:"open",slotAssignment:"manual"}),{adoptedStyleSheets:[h],innerHTML:p});get#s(){return this.closest("deck-shift")??this.ownerDocument.querySelector("deck-shift")}get#o(){return this.#s?.script()}get#c(){return this.shadowRoot.querySelector("[part=bar]")}get#h(){return this.shadowRoot.querySelector('[command="--present"]')}get#u(){return this.shadowRoot.querySelector('[command="--fullscreen"]')}get#n(){return this.shadowRoot.querySelector('[command="--record"]')}get#p(){return this.shadowRoot.querySelector('[command="--record-pause"]')}get#l(){return this.shadowRoot.querySelector(".scrub")}async connectedCallback(){let e=this.#s;await customElements.whenDefined(e.localName),this.#l.innerHTML=this.#o.map((t,a)=>`<button class="seg" command="--goto:${a+1}" data-tip="${a+1}. ${m(t.title)}" aria-label="Slide ${a+1}: ${m(t.title)}"><i></i></button>`).join(""),document.adoptedStyleSheets=[...document.adoptedStyleSheets,o];for(let t of this.shadowRoot.querySelectorAll('[command^="--"]'))t.commandForElement=e;let s=()=>{this.#h.hidden=!e.commands.includes("--present"),this.#n.hidden=!e.commands.includes("--record")};s(),e.addEventListener("deck-shift-shortcut-add",s),document.addEventListener("deck-recorder-change",({recording:t,paused:a})=>{this.#e.states[t?"add":"delete"]("recording"),this.#e.states[a?"add":"delete"]("recording-paused"),g(this.#n,t?"Stop recording":"Record","R"),g(this.#p,a?"Resume recording":"Pause recording",".")}),document.addEventListener("fullscreenchange",()=>{let t=document.fullscreenElement?"add":"delete";this.#e.states[t]("fullscreen")});let i=this.#c;i.addEventListener("mouseenter",()=>{this.#a=!0,this.#t()}),i.addEventListener("mouseleave",()=>{this.#a=!1,this.#t()}),addEventListener("touchstart",()=>this.#t(),{passive:!0}),addEventListener("mousemove",t=>{if(!t.movementX&&!t.movementY)return;this.#e.states.delete("idle"),o.replaceSync(""),clearTimeout(this.#i),this.#i=setTimeout(()=>{this.#e.states.add("idle"),o.replaceSync(x)},l),this.#t();let a=t.clientX/innerWidth,d=t.composedPath().includes(i),v=!d&&a<u?"add":"delete",f=!d&&a>1-u?"add":"delete";this.#e.states[v]("edge-l"),this.#e.states[f]("edge-r")}),document.addEventListener("mouseleave",()=>this.#e.states.delete("edge-l").delete("edge-r")),e.addEventListener("deckchange",()=>this.#d()),this.#d()}#t(e=l){this.#e.states.add("on"),clearTimeout(this.#r),this.#r=setTimeout(()=>!this.#a&&this.#e.states.delete("on"),e)}#d(){let{slide:e,step:s}=this.#s,i=this.#o[e];this.shadowRoot.querySelectorAll(".seg i").forEach((t,a)=>{t.style.scale=`${a<e?1:a>e?0:(s+1)/(i.steps+1)} 1`})}};new URL(import.meta.url).searchParams.has("nodefine")||n.define();export{n as DeckControls};
package/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "deck-controls",
3
+ "version": "0.1.0",
4
+ "description": "On-screen playback controls for a <deck-shift> slide deck.",
5
+ "keywords": [
6
+ "web-components",
7
+ "custom-elements",
8
+ "slides",
9
+ "presentation"
10
+ ],
11
+ "author": "Keith Cirkel",
12
+ "type": "module",
13
+ "exports": "./deck-controls.min.js",
14
+ "files": [
15
+ "deck-controls.min.js"
16
+ ],
17
+ "scripts": {
18
+ "build": "node esbuild.js"
19
+ },
20
+ "peerDependencies": {
21
+ "deck-shift": "^0.1.0"
22
+ }
23
+ }