spectacle
Version:
ReactJS Powered Presentation Framework
1,575 lines (1,537 loc) • 148 kB
JavaScript
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __defProps = Object.defineProperties;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __propIsEnum = Object.prototype.propertyIsEnumerable;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __spreadValues = (a, b) => {
for (var prop in b || (b = {}))
if (__hasOwnProp.call(b, prop))
__defNormalProp(a, prop, b[prop]);
if (__getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(b)) {
if (__propIsEnum.call(b, prop))
__defNormalProp(a, prop, b[prop]);
}
return a;
};
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
var __objRest = (source, exclude) => {
var target = {};
for (var prop in source)
if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
target[prop] = source[prop];
if (source != null && __getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(source)) {
if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
target[prop] = source[prop];
}
return target;
};
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var src_exports = {};
__export(src_exports, {
AnimatedProgress: () => animated_progress_default,
Appear: () => Appear,
Box: () => Box,
CodePane: () => code_pane_default,
CodeSpan: () => CodeSpan,
CommandBar: () => command_bar_default,
Deck: () => deck_default,
DeckContext: () => DeckContext,
DefaultTemplate: () => DefaultTemplate,
FitText: () => FitText,
FlexBox: () => FlexBox,
FullScreen: () => fullscreen_default,
FullSizeImage: () => FullSizeImage,
Grid: () => Grid,
Heading: () => Heading,
Image: () => Image,
Link: () => Link,
ListItem: () => ListItem,
Markdown: () => Markdown,
MarkdownPreHelper: () => MarkdownPreHelper,
MarkdownSlide: () => MarkdownSlide,
MarkdownSlideSet: () => MarkdownSlideSet,
Notes: () => notes_default,
OrderedList: () => OrderedList,
Progress: () => progress_default,
Quote: () => Quote,
Slide: () => slide_default,
SlideContext: () => SlideContext,
SlideLayout: () => slide_layout_default,
SpectacleLogo: () => SpectacleLogo,
Stepper: () => Stepper,
Table: () => Table,
TableBody: () => TableBody,
TableCell: () => TableCell,
TableHeader: () => TableHeader,
TableRow: () => TableRow,
Text: () => Text,
UnorderedList: () => UnorderedList,
codePaneThemes: () => codePaneThemes,
defaultTheme: () => default_theme_default,
defaultTransition: () => defaultTransition,
fadeTransition: () => fadeTransition,
indentNormalizer: () => indent_normalizer_default,
isolateNotes: () => isolateNotes,
mdxComponentMap: () => mdx_component_mapper_default,
removeNotes: () => removeNotes,
slideTransition: () => slideTransition,
useMousetrap: () => useMousetrap,
useSteps: () => useSteps
});
module.exports = __toCommonJS(src_exports);
// src/components/deck/index.tsx
var import_react17 = require("react");
// src/components/deck/default-deck.tsx
var import_react9 = require("react");
// src/components/deck/deck.tsx
var import_react7 = require("react");
var import_styled_components2 = __toESM(require("styled-components"));
// src/hooks/use-slides.tsx
var import_react = require("react");
var import_jsx_runtime = require("react/jsx-runtime");
var PLACEHOLDER_CLASS_NAME = "spectacle-v7-slide";
function useCollectSlides() {
const [initialized, setInitialized] = (0, import_react.useState)(false);
const [slideContainer, setSlideContainer] = (0, import_react.useState)();
const [slideIds, setSlideIds] = (0, import_react.useState)([]);
const [slideIdsOfSlidesWithTemplates, setSlideIdsOfSlidesWithTemplates] = (0, import_react.useState)(/* @__PURE__ */ new Set());
(0, import_react.useEffect)(() => {
if (!slideContainer)
return;
const slides = slideContainer.getElementsByClassName(
PLACEHOLDER_CLASS_NAME
);
const nextSlideIds = [];
const nextSlideIdsOfSlidesWithTemplates = /* @__PURE__ */ new Set();
for (const placeholderNode of slides) {
const { slideId, slideHasTemplate } = placeholderNode.dataset;
if (slideId !== void 0) {
nextSlideIds.push(slideId);
if (slideHasTemplate === "true") {
nextSlideIdsOfSlidesWithTemplates.add(slideId);
}
}
}
setSlideIds(nextSlideIds);
setSlideIdsOfSlidesWithTemplates(nextSlideIdsOfSlidesWithTemplates);
setInitialized(true);
}, [slideContainer]);
return [
setSlideContainer,
slideIds,
slideIdsOfSlidesWithTemplates,
initialized
];
}
function useSlide(doesSlideHaveTemplate, userProvidedId) {
const id = (0, import_react.useId)();
const [slideId] = (0, import_react.useState)(userProvidedId || id);
return {
slideId,
placeholder: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
"div",
{
className: PLACEHOLDER_CLASS_NAME,
"data-slide-id": slideId,
"data-slide-has-template": doesSlideHaveTemplate
}
)
};
}
// src/hooks/use-aspect-ratio-fitting.ts
var import_react2 = require("react");
var import_use_resize_observer = __toESM(require("use-resize-observer"));
function useAspectRatioFitting({
targetWidth = 1366,
targetHeight = 768
}) {
const containerRef = (0, import_react2.useRef)(null);
const [scaleFactor, setScaleFactor] = (0, import_react2.useState)(1);
const [transformOrigin, setTransformOrigin] = (0, import_react2.useState)({ x: 0, y: 0 });
const recalculate = (0, import_react2.useCallback)(
({ width, height }) => {
const containerWidth = Number(width) || 0.01;
const containerHeight = Number(height) || 0.01;
const containerRatio = containerWidth / containerHeight;
const targetRatio = targetWidth / targetHeight;
const useVertical = containerRatio > targetRatio;
const scaleFactor2 = useVertical ? containerHeight / targetHeight : containerWidth / targetWidth;
const scaledWidth = targetWidth * scaleFactor2;
const scaledHeight = targetHeight * scaleFactor2;
let x0 = 0;
if (useVertical) {
x0 = 0.5 * (containerWidth - scaledWidth);
x0 /= 1 - scaleFactor2;
}
let y0 = 0;
if (!useVertical) {
y0 = 0.5 * (containerHeight - scaledHeight);
y0 /= 1 - scaleFactor2;
}
setScaleFactor(scaleFactor2);
setTransformOrigin({ x: x0, y: y0 });
},
[targetWidth, targetHeight]
);
(0, import_react2.useEffect)(() => {
if (!containerRef || !containerRef.current)
return;
const rects = containerRef.current.getClientRects();
recalculate(rects[0]);
}, [targetWidth, targetHeight, recalculate]);
(0, import_use_resize_observer.default)({
ref: containerRef,
onResize: recalculate
});
const styles = {
position: "relative",
width: targetWidth,
height: targetHeight,
scaleFactor,
transform: `scale(${scaleFactor})`,
transformOrigin: `${transformOrigin.x}px ${transformOrigin.y}px`
};
return [containerRef, styles];
}
// src/hooks/use-deck-state.ts
var import_react3 = require("react");
var import_merge_anything = require("merge-anything");
// src/utils/clamp.ts
function toFiniteNumber(value) {
if (!value || isNaN(value)) {
return 0;
} else if (value === Infinity || value === -Infinity) {
const sign = value < 0 ? -1 : 1;
return sign * Number.MAX_SAFE_INTEGER;
}
return value;
}
function clamp(number, lower, upper) {
if (isNaN(number)) {
return NaN;
}
let finiteNumber = toFiniteNumber(number);
if (finiteNumber === finiteNumber) {
if (upper !== void 0) {
finiteNumber = finiteNumber <= upper ? finiteNumber : upper;
}
if (lower !== void 0) {
finiteNumber = finiteNumber >= lower ? finiteNumber : lower;
}
}
return finiteNumber;
}
// src/hooks/use-deck-state.ts
var GOTO_FINAL_STEP = null;
var initialDeckState = {
initialized: false,
navigationDirection: 0,
pendingView: {
slideIndex: 0,
stepIndex: 0
},
activeView: {
slideIndex: 0,
stepIndex: 0
}
};
function deckReducer(state, { type, payload = {} }) {
var _a;
switch (type) {
case "INITIALIZE_TO":
return {
navigationDirection: 0,
activeView: (0, import_merge_anything.merge)(state.activeView, payload),
pendingView: (0, import_merge_anything.merge)(state.pendingView, payload),
initialized: true
};
case "SKIP_TO":
const navigationDirection = (() => {
if ("slideIndex" in payload && payload.slideIndex) {
return clamp(payload.slideIndex - state.activeView.slideIndex, -1, 1);
}
return null;
})();
return __spreadProps(__spreadValues({}, state), {
navigationDirection: navigationDirection || state.navigationDirection,
pendingView: (0, import_merge_anything.merge)(state.pendingView, payload)
});
case "STEP_FORWARD":
return __spreadProps(__spreadValues({}, state), {
navigationDirection: 1,
pendingView: (0, import_merge_anything.merge)(state.pendingView, {
stepIndex: state.pendingView.stepIndex + 1
})
});
case "STEP_BACKWARD":
return __spreadProps(__spreadValues({}, state), {
navigationDirection: -1,
pendingView: (0, import_merge_anything.merge)(state.pendingView, {
stepIndex: state.pendingView.stepIndex - 1
})
});
case "ADVANCE_SLIDE":
return __spreadProps(__spreadValues({}, state), {
navigationDirection: 1,
pendingView: (0, import_merge_anything.merge)(state.pendingView, {
stepIndex: 0,
slideIndex: state.pendingView.slideIndex + 1
})
});
case "REGRESS_SLIDE":
return __spreadProps(__spreadValues({}, state), {
navigationDirection: -1,
pendingView: (0, import_merge_anything.merge)(state.pendingView, {
stepIndex: (_a = payload == null ? void 0 : payload.stepIndex) != null ? _a : GOTO_FINAL_STEP,
slideIndex: state.pendingView.slideIndex - 1
})
});
case "COMMIT_TRANSITION":
const pendingView = (0, import_merge_anything.merge)(state.pendingView, payload);
return __spreadProps(__spreadValues({}, state), {
pendingView,
activeView: (0, import_merge_anything.merge)(state.activeView, pendingView)
});
case "CANCEL_TRANSITION":
return __spreadProps(__spreadValues({}, state), {
pendingView: (0, import_merge_anything.merge)(state.pendingView, state.activeView)
});
default:
return state;
}
}
function useDeckState(userProvidedInitialState) {
const [
{ initialized, navigationDirection, pendingView, activeView },
dispatch
] = (0, import_react3.useReducer)(deckReducer, {
initialized: initialDeckState.initialized,
navigationDirection: initialDeckState.navigationDirection,
pendingView: __spreadValues(__spreadValues({}, initialDeckState.pendingView), userProvidedInitialState),
activeView: __spreadValues(__spreadValues({}, initialDeckState.activeView), userProvidedInitialState)
});
const actions = (0, import_react3.useMemo)(
() => ({
initializeTo: (payload) => dispatch({ type: "INITIALIZE_TO", payload }),
skipTo: (payload) => dispatch({ type: "SKIP_TO", payload }),
stepForward: () => dispatch({ type: "STEP_FORWARD" }),
stepBackward: () => dispatch({ type: "STEP_BACKWARD" }),
advanceSlide: () => dispatch({ type: "ADVANCE_SLIDE" }),
regressSlide: (payload) => dispatch({ type: "REGRESS_SLIDE", payload }),
commitTransition: (payload) => dispatch({ type: "COMMIT_TRANSITION", payload }),
cancelTransition: () => dispatch({ type: "CANCEL_TRANSITION" })
}),
[dispatch]
);
return __spreadValues({
initialized,
navigationDirection,
pendingView,
activeView
}, actions);
}
// src/hooks/use-mousetrap.ts
var import_react4 = require("react");
var import_mousetrap = __toESM(require("mousetrap"));
function useMousetrap(keybinds, deps) {
(0, import_react4.useEffect)(() => {
for (const combo in keybinds) {
const callback = keybinds[combo];
if (typeof callback !== "function") {
throw new TypeError(
`Expected type 'function' in useMousetrap for combo '${combo}', but got ${typeof callback}`
);
}
import_mousetrap.default.bind(combo, callback);
}
return () => {
for (const combo in keybinds) {
import_mousetrap.default.unbind(combo);
}
};
}, [keybinds, ...deps]);
}
// src/hooks/use-location-sync.ts
var import_react5 = require("react");
var import_history = require("history");
var import_query_string = __toESM(require("query-string"));
var import_react_fast_compare = __toESM(require("react-fast-compare"));
var import_merge_anything2 = require("merge-anything");
function defaultMergeLocation(object, ...sources) {
return (0, import_merge_anything2.mergeAndCompare)(
(left, right, key) => {
switch (key) {
case "search":
if (!left)
return right;
return "?" + import_query_string.default.stringify(__spreadValues(__spreadValues({}, import_query_string.default.parse(left)), import_query_string.default.parse(right)));
default:
return (0, import_merge_anything2.merge)(left, right);
}
},
object,
...sources
);
}
function useLocationSync({
setState,
mapStateToLocation: mapStateToLocation2,
mapLocationToState: mapLocationToState2,
disableInteractivity = false,
mergeLocation = defaultMergeLocation,
historyFactory = import_history.createBrowserHistory
}) {
const [history] = (0, import_react5.useState)(() => {
return typeof document !== "undefined" ? historyFactory() : null;
});
const [initialized, setInitialized] = (0, import_react5.useState)(false);
(0, import_react5.useEffect)(() => {
if (!initialized && disableInteractivity)
return;
return history == null ? void 0 : history.listen(({ location }) => {
setState(mapLocationToState2(location));
});
}, [
disableInteractivity,
initialized,
history,
setState,
mapLocationToState2
]);
const syncLocation = (0, import_react5.useCallback)(
(defaultState) => {
if (disableInteractivity || !history) {
return defaultState;
}
const { location } = history;
const initialState = (0, import_merge_anything2.merge)(
defaultState,
mapLocationToState2(location)
);
const nextLocation = mergeLocation(
{},
location,
mapStateToLocation2(initialState)
);
history.replace(nextLocation);
setInitialized(true);
return initialState;
},
[
history,
mapLocationToState2,
mapStateToLocation2,
disableInteractivity,
mergeLocation
]
);
const setLocation = (0, import_react5.useCallback)(
(state) => {
if (!initialized || !history)
return;
const { location } = history;
const nextLocation = mergeLocation(
{},
location,
mapStateToLocation2(state)
);
if (!(0, import_react_fast_compare.default)(location, nextLocation)) {
history.push(nextLocation);
}
},
[history, initialized, mergeLocation, mapStateToLocation2]
);
return [syncLocation, setLocation];
}
// src/theme/default-theme.ts
var defaultTheme = {
size: {
width: 1366,
height: 768,
maxCodePaneHeight: 200
},
colors: {
primary: "#ebe5da",
secondary: "#fc6986",
tertiary: "#1e2852",
quaternary: "#ffc951",
quinary: "#8bddfd"
},
fonts: {
header: '"Helvetica Neue", Helvetica, Arial, sans-serif',
text: '"Helvetica Neue", Helvetica, Arial, sans-serif',
monospace: '"Consolas", "Menlo", monospace'
},
fontSizes: {
h1: "72px",
h2: "64px",
h3: "56px",
text: "44px",
monospace: "20px"
},
space: [16, 24, 32]
};
var default_theme_default = defaultTheme;
// src/theme/print-theme.ts
var printTheme = {
colors: {
primary: "#777",
secondary: "#000",
tertiary: "#fff",
quaternary: "#000000",
quinary: "#000000"
}
};
var print_theme_default = printTheme;
// src/theme/index.ts
var mergeKeys = (base, override) => Object.keys(override || {}).reduce(
(merged, key) => {
merged[key] = __spreadValues(__spreadValues({}, merged[key]), override[key]);
return merged;
},
__spreadValues({}, base)
);
function mergeTheme({ theme, printMode }) {
const merged = mergeKeys(default_theme_default, theme);
return printMode ? mergeKeys(merged, print_theme_default) : merged;
}
// src/location-map-fns/query-string.ts
var query_string_exports = {};
__export(query_string_exports, {
mapLocationToState: () => mapLocationToState,
mapStateToLocation: () => mapStateToLocation
});
var import_query_string2 = require("query-string");
function mapLocationToState(location) {
const { search: queryString } = location;
const { slideIndex: rawSlideIndex, stepIndex: rawStepIndex } = (0, import_query_string2.parse)(queryString);
const nextState = {};
if (rawSlideIndex === void 0) {
return nextState;
}
nextState.slideIndex = Number(rawSlideIndex);
if (isNaN(nextState.slideIndex)) {
throw new Error(
`Invalid slide index in URL query string: '${queryString}'`
);
}
if (rawStepIndex === "final") {
nextState.stepIndex = GOTO_FINAL_STEP;
} else if (rawStepIndex !== void 0) {
nextState.stepIndex = Number(rawStepIndex);
if (isNaN(nextState.stepIndex)) {
throw new Error(
`Invalid step index in URL query string: '${queryString}'`
);
}
}
return nextState;
}
function mapStateToLocation(state) {
const { slideIndex, stepIndex } = state;
const query = {};
if (typeof slideIndex !== "number") {
return query;
}
query.slideIndex = String(slideIndex);
if (typeof stepIndex === "number") {
query.stepIndex = String(stepIndex);
} else if (stepIndex === GOTO_FINAL_STEP) {
query.stepIndex = "final";
}
return {
search: "?" + (0, import_query_string2.stringify)(query)
};
}
// src/components/deck/deck-styles.ts
function overviewFrameStyle({
overviewScale,
nativeSlideWidth,
nativeSlideHeight
}) {
return {
margin: "1rem",
width: `${overviewScale * nativeSlideWidth}px`,
height: `${overviewScale / (nativeSlideWidth / nativeSlideHeight) * nativeSlideWidth}px`,
display: "block",
transform: "none",
position: "relative"
};
}
function overviewWrapperStyle({
overviewScale
}) {
return {
width: `${100 / overviewScale}%`,
height: `${100 / overviewScale}%`,
transform: `scale(${overviewScale})`,
transformOrigin: "0px 0px",
position: "absolute"
};
}
function printFrameStyle({
nativeSlideWidth,
nativeSlideHeight,
printScale
}) {
return {
margin: "0",
width: `${printScale * nativeSlideWidth}px`,
height: `${printScale / (nativeSlideWidth / nativeSlideHeight) * nativeSlideWidth}px`,
display: "block",
transform: "none",
position: "relative",
breakAfter: "page"
};
}
function printWrapperStyle({
printScale
}) {
return {
width: `${100 / printScale}%`,
height: `${100 / printScale}%`,
transform: `scale(${printScale})`,
transformOrigin: "0px 0px",
position: "absolute"
};
}
// src/utils/use-auto-play.ts
var import_react6 = require("react");
var useAutoPlay = ({
enabled = false,
loop = false,
stepForward,
interval = 1e3
}) => {
const stepFn = (0, import_react6.useRef)(stepForward);
stepFn.current = stepForward;
(0, import_react6.useEffect)(() => {
if (enabled) {
const id = setInterval(() => {
stepFn.current();
}, interval);
return () => clearInterval(id);
}
}, [enabled, interval, loop]);
};
// src/components/transitions/index.ts
var STAGE_RIGHT = "translateX(-100%)";
var CENTER_STAGE = "translateX(0%)";
var STAGE_LEFT = "translateX(100%)";
var fadeTransition = {
from: {
opacity: 0
},
enter: {
opacity: 1
},
leave: {
opacity: 0
}
};
var slideTransition = {
from: {
transform: STAGE_LEFT
},
enter: {
transform: CENTER_STAGE
},
leave: {
transform: STAGE_RIGHT
}
};
var defaultTransition = slideTransition;
// src/components/template-wrapper.tsx
var import_styled_components = __toESM(require("styled-components"));
var TemplateWrapper = import_styled_components.default.div`
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
pointer-events: none;
`;
var template_wrapper_default = TemplateWrapper;
// src/components/deck/deck.tsx
var import_kbar = require("kbar");
// src/utils/constants.ts
var SYSTEM_FONT = '-apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Helvetica Neue", Helvetica, sans-serif';
var KEYBOARD_SHORTCUTS = {
DEFAULT_MODE: "mod+shift+d",
PRESENTER_MODE: "mod+shift+p",
OVERVIEW_MODE: "mod+shift+o",
PRINT_MODE: "mod+shift+r",
EXPORT_MODE: "mod+shift+e",
TAB_FORWARD_OVERVIEW_MODE: "tab",
TAB_BACKWARD_OVERVIEW_MODE: "shift+tab",
SELECT_SLIDE_OVERVIEW_MODE: "enter",
NEXT_SLIDE: "right",
PREVIOUS_SLIDE: "left"
};
var KEYBOARD_SHORTCUTS_IDS = {
DEFAULT_MODE: "DEFAULT_MODE",
PRESENTER_MODE: "PRESENTER_MODE",
OVERVIEW_MODE: "OVERVIEW_MODE",
PRINT_MODE: "PRINT_MODE",
EXPORT_MODE: "EXPORT_MODE",
TAB_FORWARD_OVERVIEW_MODE: "TAB_FORWARD_OVERVIEW_MODE",
TAB_BACKWARD_OVERVIEW_MODE: "TAB_BACKWARD_OVERVIEW_MODE",
SELECT_SLIDE_OVERVIEW_MODE: "SELECT_SLIDE_OVERVIEW_MODE",
NEXT_SLIDE: "NEXT_SLIDE",
PREVIOUS_SLIDE: "PREVIOUS_SLIDE"
};
var SPECTACLE_MODES = {
DEFAULT_MODE: "DEFAULT_MODE",
PRESENTER_MODE: "PRESENTER_MODE",
OVERVIEW_MODE: "OVERVIEW_MODE",
PRINT_MODE: "PRINT_MODE",
EXPORT_MODE: "EXPORT_MODE"
};
// src/components/deck/deck.tsx
var import_jsx_runtime2 = require("react/jsx-runtime");
var DeckContext = (0, import_react7.createContext)(null);
DeckContext.displayName = "DeckContext";
var noop = () => {
};
var DEFAULT_PRINT_SCALE = 1;
var DEFAULT_OVERVIEW_SCALE = 0.25;
var Portal = import_styled_components2.default.div(
({ fitAspectRatioStyle, overviewMode, printMode }) => [
!printMode && { overflow: "hidden" },
!printMode && fitAspectRatioStyle,
overviewMode && {
display: "flex",
flexWrap: "wrap",
justifyContent: "flex-start",
alignItems: "flex-start",
alignContent: "flex-start",
transform: "scale(1)",
overflowY: "scroll",
width: "100%",
height: "100%"
},
printMode && {
display: "block"
}
]
);
var DeckInternal = (0, import_react7.forwardRef)(
(_a, ref) => {
var _b = _a, {
id: userProvidedId,
className = "",
backdropStyle: userProvidedBackdropStyle,
overviewMode = false,
printMode = false,
exportMode = false,
overviewScale = DEFAULT_OVERVIEW_SCALE,
printScale = DEFAULT_PRINT_SCALE,
template,
theme: _c = {}
} = _b, _d = _c, {
Backdrop: UserProvidedBackdropComponent,
backdropStyle: themeProvidedBackdropStyle = {
position: "fixed",
top: 0,
left: 0,
width: "100vw",
height: "100vh"
},
suppressBackdropFallback: themeSuppressBackdropFallback
} = _d, restTheme = __objRest(_d, [
"Backdrop",
"backdropStyle",
"suppressBackdropFallback"
]), {
onSlideClick = noop,
onMobileSlide = noop,
disableInteractivity = false,
notePortalNode,
useAnimations = true,
children,
onActiveStateChange: onActiveStateChangeExternal = noop,
initialState: initialDeckState2 = {
slideIndex: 0,
stepIndex: 0
},
suppressBackdropFallback = false,
autoPlay = false,
autoPlayLoop = false,
autoPlayInterval = 1e3,
transition = defaultTransition,
backgroundImage
} = _b;
const id = (0, import_react7.useId)();
const [deckId] = (0, import_react7.useState)(userProvidedId || id);
const {
width: nativeSlideWidth = default_theme_default.size.width,
height: nativeSlideHeight = default_theme_default.size.height
} = restTheme.size || {};
const {
initialized,
pendingView,
activeView,
navigationDirection,
initializeTo,
skipTo,
stepForward,
stepBackward,
advanceSlide,
regressSlide,
commitTransition,
cancelTransition
} = useDeckState(initialDeckState2);
const [
setPlaceholderContainer,
slideIds,
slideIdsWithTemplates,
slideIdsInitialized
] = useCollectSlides();
(0, import_react7.useImperativeHandle)(
ref,
() => ({
initialized,
activeView,
initializeTo,
skipTo,
stepForward,
stepBackward,
advanceSlide,
regressSlide,
numberOfSlides: slideIds.length
}),
[
initialized,
activeView,
initializeTo,
skipTo,
stepForward,
stepBackward,
advanceSlide,
regressSlide,
slideIds
]
);
(0, import_kbar.useRegisterActions)(
!disableInteractivity ? [
{
id: KEYBOARD_SHORTCUTS_IDS.NEXT_SLIDE,
name: "Next Slide",
keywords: "next",
perform: () => stepForward(),
section: "Slide"
},
{
id: KEYBOARD_SHORTCUTS_IDS.PREVIOUS_SLIDE,
name: "Previous Slide",
keywords: "previous",
perform: () => stepBackward(),
section: "Slide"
},
{
id: "Restart Presentation",
name: "Restart Presentation",
keywords: "restart",
perform: () => skipTo({
slideIndex: 0,
stepIndex: 0
}),
section: "Slide"
}
] : []
);
useMousetrap(
disableInteractivity ? {} : {
left: () => stepBackward(),
right: () => stepForward()
},
[]
);
const [syncLocation, onActiveStateChange] = useLocationSync(__spreadValues({
disableInteractivity,
setState: skipTo
}, query_string_exports));
(0, import_react7.useEffect)(() => {
if (!initialized)
return;
onActiveStateChange(activeView);
onActiveStateChangeExternal(activeView);
}, [
initialized,
activeView,
onActiveStateChange,
onActiveStateChangeExternal
]);
(0, import_react7.useEffect)(() => {
const initialView = syncLocation({
slideIndex: 0,
stepIndex: 0
});
initializeTo(initialView);
}, [initializeTo, syncLocation]);
useAutoPlay({
enabled: autoPlay,
loop: autoPlayLoop,
interval: autoPlayInterval,
stepForward
});
const handleSlideClick = (0, import_react7.useCallback)(
(e, slideId) => {
const slideIndex = slideIds.indexOf(slideId);
onSlideClick(e, slideIndex);
},
[onSlideClick, slideIds]
);
const activeSlideId = slideIds[activeView.slideIndex];
const pendingSlideId = slideIds[pendingView.slideIndex];
const fullyInitialized = initialized && slideIdsInitialized;
const [slidePortalNode, setSlidePortalNode] = (0, import_react7.useState)();
const [backdropRef, fitAspectRatioStyle] = useAspectRatioFitting({
targetWidth: nativeSlideWidth,
targetHeight: nativeSlideHeight
});
const frameStyle = (0, import_react7.useMemo)(() => {
const options = {
printScale,
overviewScale,
nativeSlideWidth,
nativeSlideHeight
};
if (overviewMode) {
return overviewFrameStyle(options);
} else if (printMode) {
return printFrameStyle(options);
}
return {};
}, [
nativeSlideHeight,
nativeSlideWidth,
overviewMode,
overviewScale,
printMode,
printScale
]);
const wrapperStyle = (0, import_react7.useMemo)(() => {
if (overviewMode) {
return overviewWrapperStyle({ overviewScale });
} else if (printMode) {
return printWrapperStyle({ printScale });
}
return {};
}, [overviewMode, overviewScale, printMode, printScale]);
const backdropStyle = __spreadValues(__spreadValues({}, themeProvidedBackdropStyle), userProvidedBackdropStyle);
const BackdropComponent = UserProvidedBackdropComponent || "div";
if (!backdropStyle["background"] && !backdropStyle["backgroundColor"] && !UserProvidedBackdropComponent && !suppressBackdropFallback && !themeSuppressBackdropFallback) {
backdropStyle["backgroundColor"] = "black";
}
const doesCurrentSlideHaveItsOwnTemplate = slideIdsWithTemplates.has(activeSlideId);
const templateElement = typeof template === "function" ? template({
slideNumber: activeView.slideIndex + 1,
numberOfSlides: slideIds.length
}) : template;
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
import_styled_components2.ThemeProvider,
{
theme: mergeTheme({
theme: restTheme,
printMode: printMode && !exportMode
}),
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
BackdropComponent,
{
ref: backdropRef,
className,
style: __spreadProps(__spreadValues({}, backdropStyle), {
overflow: "hidden"
}),
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
DeckContext.Provider,
{
value: {
deckId,
slideCount: slideIds.length,
slideIds,
useAnimations,
slidePortalNode,
onSlideClick: handleSlideClick,
onMobileSlide,
theme: restTheme,
autoPlayLoop,
navigationDirection,
frameOverrideStyle: frameStyle,
wrapperOverrideStyle: wrapperStyle,
backdropNode: backdropRef.current,
notePortalNode,
initialized: fullyInitialized,
activeView: __spreadProps(__spreadValues({}, activeView), {
slideId: activeSlideId
}),
pendingView: __spreadProps(__spreadValues({}, pendingView), {
slideId: pendingSlideId
}),
skipTo,
stepForward,
stepBackward,
advanceSlide,
regressSlide,
commitTransition,
cancelTransition,
transition,
template,
backgroundImage,
inOverviewMode: overviewMode,
inPrintMode: printMode
},
children: [
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
Portal,
{
ref: setSlidePortalNode,
overviewMode,
printMode,
fitAspectRatioStyle,
children: !doesCurrentSlideHaveItsOwnTemplate && !overviewMode && !printMode && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
template_wrapper_default,
{
style: __spreadProps(__spreadValues({}, wrapperStyle), {
// Slides are appended to the parent as they are portaled in and end up later in
// the source order. Adding zIndex to the template to overlay the sibling slides
// once they have been portaled in.
zIndex: 1
}),
children: templateElement
}
)
}
),
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { ref: setPlaceholderContainer, style: { display: "none" }, children })
]
}
)
}
)
}
);
}
);
DeckInternal.displayName = "Deck";
var Deck = DeckInternal;
Deck.displayName = "Deck";
// src/hooks/use-broadcast-channel.ts
var import_react8 = require("react");
var import_broadcast_channel = require("broadcast-channel");
var noop2 = () => {
};
var safeWindow = {};
if (typeof window !== "undefined") {
safeWindow = window;
}
var BroadcastChannel = safeWindow.BroadcastChannel || import_broadcast_channel.BroadcastChannel;
function useBroadcastChannel(channelName, onMessage = noop2, deps = []) {
const broadcasterId = (0, import_react8.useId)();
const channel = (0, import_react8.useRef)();
(0, import_react8.useEffect)(() => {
channel.current = new BroadcastChannel(channelName);
return () => {
var _a;
(_a = channel.current) == null ? void 0 : _a.close();
};
}, [channelName]);
const postMessage = (0, import_react8.useCallback)(
(type, payload = {}) => {
var _a;
const message = {
type,
payload,
meta: { sender: broadcasterId }
};
const rawMessage = JSON.stringify(message);
(_a = channel.current) == null ? void 0 : _a.postMessage(rawMessage);
},
[broadcasterId]
);
const userMessageHandlerRef = (0, import_react8.useRef)(onMessage);
(0, import_react8.useEffect)(() => {
userMessageHandlerRef.current = onMessage;
}, [...deps, postMessage]);
(0, import_react8.useEffect)(() => {
var _a;
if (!channel.current)
return;
const messageHandler = (event) => {
const rawMessage = event.data;
const message = JSON.parse(rawMessage);
userMessageHandlerRef.current(message);
};
(_a = channel.current) == null ? void 0 : _a.addEventListener("message", messageHandler);
return () => {
var _a2;
(_a2 = channel.current) == null ? void 0 : _a2.removeEventListener("message", messageHandler);
};
}, [postMessage]);
return [postMessage, broadcasterId];
}
// src/components/deck/default-deck.tsx
var import_jsx_runtime3 = require("react/jsx-runtime");
var DefaultDeck = (props) => {
const _a = props, {
overviewMode = false,
printMode = false,
exportMode = false,
toggleMode,
children
} = _a, rest = __objRest(_a, [
"overviewMode",
"printMode",
"exportMode",
"toggleMode",
"children"
]);
const deck = (0, import_react9.useRef)(null);
const [postMessage] = useBroadcastChannel(
"spectacle_presenter_bus",
(message) => {
if (message.type !== "SYNC")
return;
const nextView = message.payload;
if (deck.current.initialized) {
deck.current.skipTo(nextView);
} else {
deck.current.initializeTo(nextView);
}
}
);
(0, import_react9.useEffect)(() => {
postMessage("SYNC_REQUEST");
}, [postMessage]);
useMousetrap(
overviewMode ? {
[KEYBOARD_SHORTCUTS.TAB_FORWARD_OVERVIEW_MODE]: () => deck.current.advanceSlide(),
[KEYBOARD_SHORTCUTS.TAB_BACKWARD_OVERVIEW_MODE]: () => deck.current.regressSlide({
stepIndex: 0
}),
[KEYBOARD_SHORTCUTS.SELECT_SLIDE_OVERVIEW_MODE]: () => toggleMode({
newMode: SPECTACLE_MODES.DEFAULT_MODE
})
} : {},
[]
);
const onSlideClick = (0, import_react9.useCallback)(
(e, slideIndex) => {
if (overviewMode) {
toggleMode({
e,
newMode: SPECTACLE_MODES.DEFAULT_MODE,
senderSlideIndex: +slideIndex
});
}
},
[overviewMode, toggleMode]
);
const onMobileSlide = (e) => {
if (navigator.maxTouchPoints < 1 || !deck.current)
return;
switch (e.dir) {
case "Left":
deck.current.stepForward();
break;
case "Right":
deck.current.regressSlide();
break;
}
};
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
DeckInternal,
__spreadProps(__spreadValues({
overviewMode,
onSlideClick,
onMobileSlide,
printMode,
exportMode,
ref: deck
}, rest), {
children
})
);
};
var default_deck_default = DefaultDeck;
// src/components/presenter-mode/index.tsx
var import_react13 = require("react");
var import_styled_components7 = __toESM(require("styled-components"));
// src/components/presenter-mode/components.tsx
var import_styled_components3 = __toESM(require("styled-components"));
var PresenterDeckContainer = import_styled_components3.default.div`
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
flex-direction: row;
background-color: #181818;
overflow: hidden;
color: white;
`;
var NotesColumn = import_styled_components3.default.div`
padding: 0;
display: flex;
flex-direction: column;
width: 50%;
border-right: 1px solid black;
`;
var PreviewColumn = import_styled_components3.default.div`
background-color: black;
display: flex;
flex-direction: column;
height: 100%;
width: 50%;
> :first-child {
margin-bottom: 0.5em;
}
`;
var SlideContainer = import_styled_components3.default.div`
display: flex;
flex-direction: column;
height: calc(50% - 1em);
width: 100%;
overflow: hidden;
`;
var SlideWrapper = import_styled_components3.default.div`
flex: 1;
width: 100%;
position: relative;
.spectacle-fullscreen-button {
display: none;
}
${({ small }) => small && `flex: 0.8;`}
`;
var SlideCountLabel = import_styled_components3.default.span`
background: hsla(0, 0%, 100%, 0.1);
border-radius: 4px;
font-size: 0.7em;
padding: 1px 4px;
`;
var NotesContainer = import_styled_components3.default.div`
border-top: 1px solid black;
overflow-y: scroll;
flex: 1;
::-webkit-scrollbar {
width: 10px;
}
/* Track */
::-webkit-scrollbar-track {
background-color: #111;
}
/* Handle */
::-webkit-scrollbar-thumb {
background: #333;
border-radius: 10px;
}
`;
var deckBackdropStyles = {
currentSlide: {
width: "50vw",
height: "50vh",
left: "50vw",
top: "7vh"
},
nextSlide: {
width: "50vw",
height: "33vh",
top: "60vh",
left: "50vw"
}
};
// src/components/layout-primitives.ts
var import_styled_components4 = __toESM(require("styled-components"));
var import_styled_system = require("styled-system");
var containerPrintStyle = `
@media print {
height: inherit;
}
`;
var Box = import_styled_components4.default.div(
(0, import_styled_system.compose)(import_styled_system.layout, import_styled_system.space, import_styled_system.position, import_styled_system.color, import_styled_system.border),
containerPrintStyle
);
var FlexBox = import_styled_components4.default.div.attrs((props) => __spreadValues({
alignItems: "center",
justifyContent: "center",
display: "flex"
}, props))(
(0, import_styled_system.compose)(import_styled_system.layout, import_styled_system.space, import_styled_system.position, import_styled_system.color, import_styled_system.border, import_styled_system.flexbox),
containerPrintStyle
);
var Grid = import_styled_components4.default.div.attrs((props) => __spreadValues({
display: "grid"
}, props))((0, import_styled_system.compose)(import_styled_system.layout, import_styled_system.grid, import_styled_system.position), containerPrintStyle);
// src/components/presenter-mode/timer.tsx
var import_react12 = require("react");
// src/components/typography.tsx
var import_styled_components5 = __toESM(require("styled-components"));
var import_styled_system2 = require("styled-system");
var import_react10 = require("react");
var import_use_resize_observer2 = __toESM(require("use-resize-observer"));
var import_jsx_runtime4 = require("react/jsx-runtime");
var decoration = (0, import_styled_system2.system)({ textDecoration: true });
var Text = import_styled_components5.default.div.attrs((props) => __spreadValues({
color: "primary",
fontFamily: "text",
fontSize: "text",
textAlign: "left",
padding: 0,
margin: 0
}, props))((0, import_styled_system2.compose)(import_styled_system2.color, import_styled_system2.typography, import_styled_system2.space));
var CodeSpan = import_styled_components5.default.code.attrs((props) => __spreadValues({
fontFamily: "monospace",
fontSize: "text"
}, props))((0, import_styled_system2.compose)(import_styled_system2.color, import_styled_system2.typography, import_styled_system2.space));
var Link = import_styled_components5.default.a.attrs(
(props) => __spreadValues({
fontFamily: "text",
fontSize: "text",
textDecoration: "underline",
color: "quaternary"
}, props)
)(
(0, import_styled_system2.compose)(import_styled_system2.color, import_styled_system2.typography, import_styled_system2.space, decoration)
);
var Heading = import_styled_components5.default.div.attrs((props) => __spreadValues({
color: "secondary",
fontFamily: "header",
fontSize: "h1",
fontWeight: "bold",
textAlign: "center",
margin: 1,
padding: 0
}, props))((0, import_styled_system2.compose)(import_styled_system2.color, import_styled_system2.typography, import_styled_system2.space));
var Quote = (0, import_styled_components5.default)(
Text
).attrs((props) => __spreadValues({
color: "primary",
fontFamily: "text",
fontSize: "text",
textAlign: "left",
fontStyle: "italic",
padding: "16px 0 16px 8px",
margin: 0
}, props))`
border-left: 1px solid
${({ theme, borderColor }) => borderColor || theme.colors.secondary};
div {
margin: 0;
}
`;
var listStyle = (0, import_styled_system2.system)({
listStyleType: true
});
var OrderedList = import_styled_components5.default.ol.attrs(
(props) => __spreadValues({
color: "primary",
fontFamily: "text",
fontSize: "text",
textAlign: "left",
margin: 0
}, props)
)(
(0, import_styled_system2.compose)(import_styled_system2.color, import_styled_system2.typography, import_styled_system2.space, listStyle)
);
var UnorderedList = import_styled_components5.default.ul.attrs(
(props) => __spreadValues({
color: "primary",
fontFamily: "text",
fontSize: "text",
textAlign: "left",
margin: 0
}, props)
)(
(0, import_styled_system2.compose)(import_styled_system2.color, import_styled_system2.typography, import_styled_system2.space, listStyle)
);
var ListItem = import_styled_components5.default.li.attrs((props) => __spreadValues({
margin: 0
}, props))((0, import_styled_system2.compose)(import_styled_system2.color, import_styled_system2.typography, import_styled_system2.space));
var FitContainer = import_styled_components5.default.div`
width: 100%;
display: flex;
align-items: center;
justify-content: center;
`;
var ScalableText = (0, import_styled_components5.default)(
Text
).attrs((props) => __spreadValues({
textAlign: "center"
}, props))`
transform-origin: center;
transform: scale(${(props) => props.scale || 1});
white-space: nowrap;
`;
var FitText = (props) => {
const containerRef = (0, import_react10.useRef)(null);
const textRef = (0, import_react10.useRef)(null);
const [scale, setScale] = (0, import_react10.useState)(1);
(0, import_use_resize_observer2.default)({
ref: containerRef,
onResize: () => {
if (!containerRef.current || !textRef.current)
return;
const containerWidth = containerRef.current.offsetWidth;
const textWidth = textRef.current.offsetWidth;
if (textWidth === 0)
return;
const newScale = Math.min(containerWidth / textWidth);
setScale(newScale);
}
});
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(FitContainer, { ref: containerRef, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ScalableText, __spreadProps(__spreadValues({}, props), { ref: textRef, scale })) });
};
// src/components/internal-button.ts
var import_styled_components6 = __toESM(require("styled-components"));
var InternalButton = (0, import_styled_components6.default)("button")`
background: #333;
border: 1px solid hsla(0, 0%, 0%, 0.4);
border-radius: 2px;
color: #fff;
box-shadow: inset 1px 1px 0 hsla(0, 0%, 100%, 0.1),
1px 1px 0 hsla(0, 0%, 0%, 0.1);
padding: 3px 20px;
font-size: 14px;
font-weight: bold;
font-family: ${SYSTEM_FONT};
&:active {
box-shadow: inset 1px 1px 0 hsla(0, 0%, 0%, 0.25),
1px 1px 0 hsla(0, 0%, 0%, 0.1);
}
`;
var internal_button_default = InternalButton;
// src/utils/use-timer.ts
var import_react11 = require("react");
var useTimer = (handler, period, isActive) => {
const [timeDelay, setTimeDelay] = (0, import_react11.useState)(1);
const initialTime = (0, import_react11.useRef)();
const callBack = (0, import_react11.useRef)();
(0, import_react11.useEffect)(() => {
callBack.current = handler;
}, [handler]);
(0, import_react11.useEffect)(() => {
if (isActive) {
initialTime.current = (/* @__PURE__ */ new Date()).getTime();
const timer = setInterval(() => {
const currentTime = (/* @__PURE__ */ new Date()).getTime();
const delay = currentTime - initialTime.current;
initialTime.current = currentTime;
setTimeDelay(delay / 1e3);
callBack.current(timeDelay);
}, period);
return () => {
clearInterval(timer);
};
}
}, [period, isActive, timeDelay]);
};
// src/components/presenter-mode/timer.tsx
var import_kbar2 = require("kbar");
var import_jsx_runtime5 = require("react/jsx-runtime");
var Timer = () => {
const [timer, setTimer] = (0, import_react12.useState)(0);
const [timerStarted, setTimerStarted] = (0, import_react12.useState)(false);
const addToTimer = (0, import_react12.useCallback)((v) => setTimer((s) => s + v), []);
const toggleTimer = (0, import_react12.useCallback)(() => setTimerStarted((s) => !s), []);
const resetTimer = (0, import_react12.useCallback)(() => setTimer(0), []);
useTimer(addToTimer, 1e3, timerStarted);
const minutes = Math.floor(Math.round(timer) / 60);
(0, import_kbar2.useRegisterActions)([
{
id: "Start/Pause Timer",
name: "Start/Pause Timer",
keywords: "start pause",
perform: toggleTimer,
section: "Timer"
},
{
id: "Restart Timer",
name: "Restart Timer",
keywords: "restart",
perform: resetTimer,
section: "Timer"
}
]);
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(FlexBox, { children: [
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(FlexBox, { justifyContent: "flex-start", flex: 1, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
Text,
{
fontFamily: SYSTEM_FONT,
fontWeight: "bold",
fontSize: "2vw",
textAlign: "left",
children: `${String(minutes).padStart(2, "0")}:${String(
Math.round(timer) - minutes * 60
).padStart(2, "0")}`
}
) }),
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(internal_button_default, { onClick: toggleTimer, children: timerStarted ? "Stop Timer" : "Start Timer" }),
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Box, { width: 8 }),
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(internal_button_default, { onClick: resetTimer, children: "Reset" })
] });
};
// src/components/presenter-mode/index.tsx
var import_jsx_runtime6 = require("react/jsx-runtime");
var endOfNextSlide = ({ slideIndex }) => ({
slideIndex: slideIndex + 1,
stepIndex: GOTO_FINAL_STEP
});
var PreviewSlideWrapper = import_styled_components7.default.div(
({ visible }) => ({
visibility: visible ? "visible" : "hidden"
})
);
var PresenterMode = (props) => {
const { children, theme, backgroundImage, template } = props;
const deck = (0, import_react13.useRef)(null);
const previewDeck = (0, import_react13.useRef)(null);
const [notePortalNode, setNotePortalNode] = (0, import_react13.useState)();
const [showFinalSlide, setShowFinalSlide] = (0, import_react13.useState)(true);
const [postMessage] = useBroadcastChannel(
"spectacle_presenter_bus",
(message) => {
if (message.type === "SYNC_REQUEST") {
postMessage("SYNC", deck.current.activeView);
}
}
);
const [syncLocation, setLocation] = useLocationSync(__spreadValues({
setState: (state) => deck.current.skipTo(state)
}, query_string_exports));
const onActiveStateChange = (0, import_react13.useCallback)(
(activeView) => {
var _a, _b;
setLocation(active