rikiki-deck 0.4.0 → 0.6.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.
@@ -1,4 +1,39 @@
1
- import { LitElement } from 'lit';
1
+ import { LitElement, type PropertyValues } from 'lit';
2
+ type Slide = HTMLElement & {
3
+ applyStep?: (step: number) => void;
4
+ render?: () => unknown;
5
+ };
6
+ /** The stable surface a plugin may touch · deliberately small so plugins never
7
+ * reach into the engine's private internals (the old approach monkey-patched
8
+ * the prototype's private methods, which broke silently on any rename). */
9
+ export interface DeckContext {
10
+ /** The deck-root element · for ad-hoc first-party coordination markers. */
11
+ readonly host: DeckRoot;
12
+ readonly current: number;
13
+ readonly step: number;
14
+ readonly slides: readonly Slide[];
15
+ requestUpdate(): void;
16
+ }
17
+ /** A deck-root extension. Register one with `deckRoot.use(plugin)` · the engine
18
+ * calls the optional hooks at the matching points. All hooks are optional so a
19
+ * plugin implements only what it needs. */
20
+ export interface DeckPlugin {
21
+ /** Unique name · registration is idempotent by this. */
22
+ name: string;
23
+ /** Run once on registration · may return a teardown run on unregister. */
24
+ setup?(ctx: DeckContext): void | (() => void);
25
+ /** Contribute to the active slide's step count · combined with the engine's
26
+ * own count (and other plugins') as a maximum. Must not call back into the
27
+ * engine's step count. */
28
+ steps?(slide: Slide, ctx: DeckContext): number;
29
+ /** React after the engine applied a step to the active slide. */
30
+ applyStep?(step: number, slide: Slide, ctx: DeckContext): void;
31
+ /** Around-advice for navigation · call `proceed()` to run the real navigation
32
+ * (optionally wrapped, e.g. inside a View Transition). Return a truthy value
33
+ * when handled · otherwise the engine navigates normally. Only the first
34
+ * registered plugin with a `navigate` hook owns navigation. */
35
+ navigate?(to: number, ctx: DeckContext, proceed: () => void): boolean | void;
36
+ }
2
37
  export declare class DeckRoot extends LitElement {
3
38
  static styles: import("lit").CSSResult;
4
39
  current: number;
@@ -6,20 +41,35 @@ export declare class DeckRoot extends LitElement {
6
41
  /** When non-null, a full-screen overlay covers the deck (clicker B/./W/,
7
42
  * keys). Pressing any key dismisses it · same convention as PowerPoint. */
8
43
  blank: 'black' | 'white' | null;
44
+ /** Set by the presenter plugin while the speaker popup is open. The main
45
+ * window is then the projected one, so its key-hint chips and nav arrows are
46
+ * noise · hide them until the popup closes (issue #6). */
47
+ presenterActive: boolean;
9
48
  overview: boolean;
10
- /** Fixed-viewport mode · the deck renders into a fixed-aspect canvas that is
11
- * letterboxed to fit any screen, so layouts never reflow between displays.
12
- * Opt-in · the default stays fluid (100vw × 100vh). */
13
- fixed: boolean;
14
- /** Logical canvas size for fixed mode · defaults to 1920 × 1080 (16:9).
15
- * Only the ratio and the rem baseline depend on these · the canvas is then
16
- * scaled by CSS to fill the window. */
49
+ /** Fluid rendering · the deck fills its box and reflows like a web page
50
+ * (no logical canvas, no zoom-to-fit scale, no letterbox). Opt-in · the
51
+ * default stays the uniform zoom-to-fit canvas. Toggleable at runtime ·
52
+ * flipping it re-applies the canvas vars, scale and letterbox both ways. */
53
+ fluid: boolean;
54
+ /** Logical canvas size · defaults to 1920 × 1080 (16:9). Only the ratio and
55
+ * the rem baseline depend on these · the canvas is then scaled uniformly to
56
+ * fill the window (see _applyScale). */
17
57
  width: number;
18
58
  height: number;
19
59
  /** Hide the bottom-left keyboard-hint chip (the ←/→ · O · P · ? row). */
20
60
  noHint: boolean;
21
61
  /** Hide the bottom-right on-screen previous/next navigation arrows. */
22
62
  noArrows: boolean;
63
+ /** Hide the bottom-right slide counter (`n / total`) · used by the presenter
64
+ * preview, which already shows the count in its own chrome. */
65
+ noCounter: boolean;
66
+ /** Passive-render mode · the deck still scales/letterboxes but wires NO
67
+ * keyboard, mouse, autoplay or presenter handlers. Used by the presenter
68
+ * preview iframes, which must not hijack keys or open a nested presenter on
69
+ * the shared BroadcastChannel · the speaker drives the real deck instead. */
70
+ preview: boolean;
71
+ /** Disable slide zoom (Ctrl/⌘+wheel, pinch, +/-/0) · on by default. */
72
+ noZoom: boolean;
23
73
  /** Optional slide transition · "slide" | "fade" | "zoom". When set, the
24
74
  * deck-transition.js plugin is fetched on first navigation. Per-slide
25
75
  * override available via `data-transition` on the slide host. */
@@ -54,7 +104,30 @@ export declare class DeckRoot extends LitElement {
54
104
  private _navDownY;
55
105
  private _wheelAccum;
56
106
  private _wheelLockUntil;
107
+ private _zoom;
108
+ private _panX;
109
+ private _panY;
110
+ private static readonly ZOOM_MAX;
111
+ private static readonly ZOOM_STEP;
112
+ private static readonly ZOOM_WHEEL_SENSITIVITY;
113
+ private _plugins;
114
+ private _ctx;
115
+ /** Build (once) the stable context object plugins receive · live getters so a
116
+ * plugin always sees the engine's current position (the object itself is
117
+ * cached, not frozen). */
118
+ private _context;
119
+ /** Register a plugin · idempotent by name. Returns an unregister function that
120
+ * runs the plugin's teardown and detaches its hooks. A freshly registered
121
+ * plugin may change step counts or element visibility, so the active slide is
122
+ * re-applied immediately (this also covers plugins attached after the deck
123
+ * has already rendered). */
124
+ use(plugin: DeckPlugin): () => void;
57
125
  firstUpdated(): void;
126
+ /** Canvas vars, scale and every listener the deck needs while connected.
127
+ * Mirror of the disconnectedCallback teardown · runs from firstUpdated on
128
+ * the initial connect, and again from connectedCallback on a re-attach
129
+ * (firstUpdated only ever runs once per element). */
130
+ private _installRuntime;
58
131
  /** Confine author `<style scoped>` blocks to their own slide. A light-DOM
59
132
  * <style> is a global stylesheet by default, so a per-slide tweak would
60
133
  * bleed across the whole deck. Wrapping its body in a native @scope rule
@@ -62,9 +135,68 @@ export declare class DeckRoot extends LitElement {
62
135
  * with no selector rewriting · plain CSS inside keeps working unchanged. */
63
136
  private _scopeSlideStyles;
64
137
  /** Publish the logical canvas size on the document root so both the
65
- * `html:has(deck-root[fixed])` font-size rule and the shadow `#stage`
66
- * (via custom-property inheritance) size against the same numbers. */
138
+ * `html:has(deck-root)` rem-baseline rule and the shadow `#stage` (via
139
+ * custom-property inheritance) size against the same numbers. */
140
+ /** True when the whole deck is fluid, or the active slide opts out of the
141
+ * fixed canvas via its own `fluid` attribute · a per-slide viewport escape
142
+ * (issue #4). It drives the canvas vars, scale and letterbox exactly like
143
+ * the deck-wide `fluid` flag, just for that one slide. */
144
+ private _effectiveFluid;
145
+ /** Reflect the active slide's per-slide fluid escape on the host · the
146
+ * `unfixed` marker that the stage CSS and the injected rem baseline key on ·
147
+ * then re-apply the canvas vars and scale for the new effective mode. Called
148
+ * on every slide change · a no-op for a deck that never uses per-slide fluid. */
149
+ private _applySlideFluid;
67
150
  private _applyCanvasVars;
151
+ /** Uniform zoom-to-fit · scale the fixed logical canvas to the largest size
152
+ * that still fits the host's own box (the viewport for a full-window deck,
153
+ * the container for an embedded one). Driven by a ResizeObserver. */
154
+ private _applyScale;
155
+ private _resizeObserver;
156
+ /** Zoom is live only in the fixed canvas and outside overlays. */
157
+ private _zoomEnabled;
158
+ /** Publish zoom + pan as custom props the #stage transform reads. */
159
+ private _applyZoom;
160
+ /** Keep the pan within bounds so the magnified stage always covers the
161
+ * viewport (no gaps); at fit (zoom 1) it forces re-centring. */
162
+ private _clampPan;
163
+ /** Zoom by a factor, keeping the point at viewport (cx, cy) fixed. */
164
+ private _zoomAt;
165
+ private _resetZoom;
166
+ private _panBy;
167
+ private _panning;
168
+ private _panLastX;
169
+ private _panLastY;
170
+ private _onPanDown;
171
+ private _onPanMove;
172
+ private _onPanUp;
173
+ /** Make the letterbox bands match the active slide's background, so a scaled
174
+ * deck blends seamlessly into the bands instead of sitting on a contrasting
175
+ * frame. A slide with no background of its own (transparent) shows the page
176
+ * surface · removing the override lets the bands fall back to that same
177
+ * surface, which stays seamless too. */
178
+ private _applyLetterbox;
179
+ /** The scaling baseline is a framework concern, not a theme one: inject it
180
+ * globally so any theme (or none) gets it. The rem unit tracks the logical
181
+ * canvas height · the #stage transform does the responsive scaling · and the
182
+ * page never scrolls (so the letterbox is the only thing outside a slide).
183
+ * Guarded by the element id (not a static) so separately-bundled copies of
184
+ * this class on one page share the same once-per-document semantics.
185
+ *
186
+ * Every rule is scoped with `:has(> body > deck-root)`, so it is inert
187
+ * unless a deck is a direct <body> child · i.e. a full-page deck. An
188
+ * embedded deck (sitting in some container) never matches, so it leaves the
189
+ * host page's scroll and rem baseline alone.
190
+ *
191
+ * The `:not([fluid])` rule carries the zoom-to-fit canvas baseline; the
192
+ * `[fluid]` rule gives a fluid deck the viewport-relative rem baseline
193
+ * instead. The `[unfixed]` rule (set while the active slide opts out via its
194
+ * own `fluid` attribute) borrows that same viewport baseline for that one
195
+ * slide · it comes last so it wins the equal-specificity tie with the
196
+ * `:not([fluid])` rule. The `height:100%` pair backs the 100% `:host`
197
+ * sizing. */
198
+ private static _injectGlobals;
199
+ connectedCallback(): void;
68
200
  disconnectedCallback(): void;
69
201
  private _startAutoplay;
70
202
  private _stopAutoplay;
@@ -111,14 +243,23 @@ export declare class DeckRoot extends LitElement {
111
243
  /** Lazy-import the overview module the first time the user opens it. */
112
244
  private _renderOverviewIfActive;
113
245
  private _maxSteps;
246
+ /** The engine's own step count for the active slide · `steps`/`data-steps`
247
+ * attribute, or a `deck-code[step-groups]` group count. Plugins extend this
248
+ * through their `steps` hook (see _maxSteps). */
249
+ private _baseMaxSteps;
114
250
  private _advance;
115
251
  private _back;
116
252
  private _goTo;
253
+ private _goToNow;
117
254
  private _goToCoords;
118
255
  private _applyActive;
119
256
  private _applyStep;
257
+ /** The bottom-right slide counter is noise in modes where it shouldn't show:
258
+ * the overview grid, a black/white blanked screen, and the cover slide (the
259
+ * title slide has no business carrying a page number). */
260
+ private _counterHidden;
120
261
  private _updateUI;
121
- updated(): void;
262
+ updated(changed: PropertyValues<this>): void;
122
263
  private _navArrows;
123
264
  render(): unknown;
124
265
  }
@@ -127,3 +268,4 @@ declare global {
127
268
  'deck-root': DeckRoot;
128
269
  }
129
270
  }
271
+ export {};
package/dist/shiki.js CHANGED
@@ -1 +1 @@
1
- var i=null,l="one-dark-pro";async function c(t){return i||(i=await(globalThis.__rikikiShiki??(await import(new URL("./vendor/shiki.js",import.meta.url).href)).createHighlighter)({themes:[t.theme],langs:t.langs}),i)}function d(t){return t.replace(/ style="[^"]*"/g,"")}async function u(t={}){let n={theme:t.theme??"one-dark-pro",langs:t.langs??["ts","js","html","css","json"]};l=n.theme;let r=await c(n),o=customElements.get("deck-code");if(!o){console.warn("[rikiki/shiki] <deck-code> is not defined yet \xB7 import rikiki first");return}let s=o.prototype,h=s._highlight;s._highlight=function(){let e=this,g=e.textContent??"";try{let a=r.codeToHtml(g,{lang:e.lang||"txt",theme:l}).replace(/^<pre[^>]*><code[^>]*>/,"").replace(/<\/code><\/pre>$/,"");e._html=d(a)}catch{h.call(this)}},document.querySelectorAll("deck-code").forEach(e=>{e._highlight?.(),e.requestUpdate?.()})}export{u as installShiki};
1
+ function n(e){let i=customElements.get("deck-code");if(!i){console.warn("[rikiki/deck-code] <deck-code> is not defined yet \xB7 import rikiki first");return}i.highlighter=e,i.rehighlightAll()}var t=null,l="one-dark-pro";async function s(e){return t||(t=await(globalThis.__rikikiShiki??(await import(new URL("./vendor/shiki.js",import.meta.url).href)).createHighlighter)({themes:[e.theme],langs:e.langs}),t)}async function d(e={}){let i={theme:e.theme??"one-dark-pro",langs:e.langs??["ts","js","html","css","json"]};l=i.theme;let r=await s(i);n((o,h)=>{try{return r.codeToHtml(o,{lang:h||"txt",theme:l}).replace(/^<pre[^>]*><code[^>]*>/,"").replace(/<\/code><\/pre>$/,"")}catch{return null}})}export{d as installShiki};