@blockslides/extension-slide 0.3.0 → 0.3.2

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/dist/index.d.mts CHANGED
@@ -24,6 +24,13 @@ interface SlideOptions {
24
24
  * @default '16x9'
25
25
  */
26
26
  defaultSize: "16x9" | "4x3" | "a4-portrait" | "a4-landscape" | "letter-portrait" | "letter-landscape" | "linkedin-banner";
27
+ /**
28
+ * Scale factor for fixed-size slides.
29
+ * Only applies when renderMode is 'fixed'.
30
+ * Helpful for shrinking/expanding fixed previews without changing size presets.
31
+ * @default 1
32
+ */
33
+ scale: number;
27
34
  /**
28
35
  * Inject @media print/@page CSS for paper sizes
29
36
  * @default true
package/dist/index.d.ts CHANGED
@@ -24,6 +24,13 @@ interface SlideOptions {
24
24
  * @default '16x9'
25
25
  */
26
26
  defaultSize: "16x9" | "4x3" | "a4-portrait" | "a4-landscape" | "letter-portrait" | "letter-landscape" | "linkedin-banner";
27
+ /**
28
+ * Scale factor for fixed-size slides.
29
+ * Only applies when renderMode is 'fixed'.
30
+ * Helpful for shrinking/expanding fixed previews without changing size presets.
31
+ * @default 1
32
+ */
33
+ scale: number;
27
34
  /**
28
35
  * Inject @media print/@page CSS for paper sizes
29
36
  * @default true
package/dist/index.js CHANGED
@@ -62,8 +62,8 @@ var slideStyles = `
62
62
  opacity: var(--slide-bg-overlay-opacity, 0.35);
63
63
  }
64
64
  `;
65
- var fixedSizeStyles = `
66
- .slide { --slide-scale: 1; }
65
+ var getFixedSizeStyles = (scale) => `
66
+ .slide { --slide-scale: ${scale}; }
67
67
  .slide[data-size="16x9"] { width: calc(1920px * var(--slide-scale)); height: calc(1080px * var(--slide-scale)); }
68
68
  .slide[data-size="4x3"] { width: calc(1600px * var(--slide-scale)); height: calc(1200px * var(--slide-scale)); }
69
69
  .slide[data-size="a4-portrait"] { width: calc(210mm * var(--slide-scale)); height: calc(297mm * var(--slide-scale)); }
@@ -146,6 +146,7 @@ var Slide = import_core.Node.create({
146
146
  injectCSS: true,
147
147
  renderMode: "fixed",
148
148
  defaultSize: "16x9",
149
+ scale: 1,
149
150
  injectPrintCSS: true,
150
151
  injectNonce: void 0,
151
152
  hoverOutline: false,
@@ -251,9 +252,14 @@ var Slide = import_core.Node.create({
251
252
  key: SlidePluginKey,
252
253
  state: {
253
254
  init: () => {
255
+ if (this.options.scale !== 1 && this.options.renderMode !== "fixed") {
256
+ console.warn(
257
+ `[Slide] The 'scale' option only applies when renderMode is 'fixed'. Current renderMode is '${this.options.renderMode}', so the scale value of ${this.options.scale} will be ignored.`
258
+ );
259
+ }
254
260
  if (this.options.injectCSS && typeof document !== "undefined") {
255
261
  (0, import_core.createStyleTag)(slideStyles, this.options.injectNonce, "slide");
256
- const sizingCss = this.options.renderMode === "dynamic" ? dynamicSizeStyles : fixedSizeStyles;
262
+ const sizingCss = this.options.renderMode === "dynamic" ? dynamicSizeStyles : getFixedSizeStyles(this.options.scale);
257
263
  if (sizingCss) {
258
264
  (0, import_core.createStyleTag)(sizingCss, this.options.injectNonce, "slide-sizes");
259
265
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/slide.ts"],"sourcesContent":["export { Slide } from \"./slide\";\nexport type { SlideOptions } from \"./slide\";\n","import { Node, mergeAttributes, createStyleTag } from \"@blockslides/core\";\nimport { Plugin, PluginKey } from \"@blockslides/pm/state\";\n\nconst slideStyles = `\n.slide {\n position: relative;\n height: var(--slide-height, 100%);\n min-height: var(--slide-min-height, 250px);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n background-color: var(--slide-bg);\n border-radius: var(--slide-border-radius);\n box-shadow: var(--slide-shadow);\n margin-bottom: var(--slide-margin-bottom);\n}\n\n/* Background helpers (driven by data attributes + inline CSS vars) */\n.slide[data-bg-mode=\"color\"] {\n background-color: var(--slide-bg-color, var(--slide-bg));\n}\n\n.slide[data-bg-mode=\"image\"],\n.slide[data-bg-mode=\"imageOverlay\"] {\n background-size: cover;\n background-repeat: no-repeat;\n background-position: center;\n}\n\n.slide[data-bg-mode=\"imageOverlay\"]::before {\n content: \"\";\n position: absolute;\n inset: 0;\n pointer-events: none;\n background-color: var(--slide-bg-overlay-color, #000);\n opacity: var(--slide-bg-overlay-opacity, 0.35);\n}\n`;\n\nconst fixedSizeStyles = `\n.slide { --slide-scale: 1; }\n.slide[data-size=\"16x9\"] { width: calc(1920px * var(--slide-scale)); height: calc(1080px * var(--slide-scale)); }\n.slide[data-size=\"4x3\"] { width: calc(1600px * var(--slide-scale)); height: calc(1200px * var(--slide-scale)); }\n.slide[data-size=\"a4-portrait\"] { width: calc(210mm * var(--slide-scale)); height: calc(297mm * var(--slide-scale)); }\n.slide[data-size=\"a4-landscape\"] { width: calc(297mm * var(--slide-scale)); height: calc(210mm * var(--slide-scale)); }\n.slide[data-size=\"letter-portrait\"] { width: calc(8.5in * var(--slide-scale)); height: calc(11in * var(--slide-scale)); }\n.slide[data-size=\"letter-landscape\"] { width: calc(11in * var(--slide-scale)); height: calc(8.5in * var(--slide-scale)); }\n.slide[data-size=\"linkedin-banner\"] { width: calc(1584px * var(--slide-scale)); height: calc(396px * var(--slide-scale)); }\n`.trim();\n\nconst dynamicSizeStyles = `\n.slide[data-size=\"16x9\"] { width: 100%; height: auto; aspect-ratio: 16 / 9; }\n.slide[data-size=\"4x3\"] { width: 100%; height: auto; aspect-ratio: 4 / 3; }\n.slide[data-size=\"a4-portrait\"] { width: 100%; height: auto; aspect-ratio: 210 / 297; }\n.slide[data-size=\"a4-landscape\"] { width: 100%; height: auto; aspect-ratio: 297 / 210; }\n.slide[data-size=\"letter-portrait\"] { width: 100%; height: auto; aspect-ratio: 8.5 / 11; }\n.slide[data-size=\"letter-landscape\"] { width: 100%; height: auto; aspect-ratio: 11 / 8.5; }\n.slide[data-size=\"linkedin-banner\"] { width: 100%; height: auto; aspect-ratio: 1584 / 396; }\n`.trim();\n\nconst printSizeStyles = `\n@media print {\n .slide[data-size=\"a4-portrait\"] { width: 210mm; height: 297mm; }\n @page { size: A4 portrait; margin: 0; }\n}\n@media print {\n .slide[data-size=\"a4-landscape\"] { width: 297mm; height: 210mm; }\n @page { size: A4 landscape; margin: 0; }\n}\n@media print {\n .slide[data-size=\"letter-portrait\"] { width: 8.5in; height: 11in; }\n @page { size: Letter portrait; margin: 0; }\n}\n@media print {\n .slide[data-size=\"letter-landscape\"] { width: 11in; height: 8.5in; }\n @page { size: Letter landscape; margin: 0; }\n}\n`.trim();\n\nconst hoverableSelector = [\n '[data-node-type]:not([data-node-type=\"slide\"])',\n \"p\",\n \"h1\",\n \"h2\",\n \"h3\",\n \"h4\",\n \"h5\",\n \"h6\",\n \"blockquote\",\n \"ul\",\n \"ol\",\n \"li\",\n \"pre\",\n \"figure\",\n \"table\",\n].join(\", \");\n\nconst hoverOutlineStyles = `\n.slide[data-hover-outline=\"on\"]\n :where(${hoverableSelector}):hover:not(:has(:hover)) {\n outline: var(--slide-hover-outline-width, 1.5px) solid var(--slide-hover-outline-color, rgba(59, 130, 246, 0.65));\n outline-offset: var(--slide-hover-outline-offset, 4px);\n transition: outline-color 120ms ease, outline-width 120ms ease;\n}\n\n/* Cascade: when hovering a container, outline its descendant blocks too */\n.slide[data-hover-outline=\"on\"][data-hover-outline-cascade=\"on\"]\n :where(${hoverableSelector}):hover\n :where(${hoverableSelector}) {\n outline: var(--slide-hover-outline-width, 1.5px) solid var(--slide-hover-outline-color, rgba(59, 130, 246, 0.65));\n outline-offset: var(--slide-hover-outline-offset, 4px);\n transition: outline-color 120ms ease, outline-width 120ms ease;\n}\n`.trim();\n\nexport interface SlideOptions {\n /**\n * The HTML attributes for a slide node.\n * @default {}\n * @example { class: 'foo' }\n */\n HTMLAttributes: Record<string, any>;\n /**\n * Whether to inject CSS styles\n * @default true\n */\n injectCSS: boolean;\n /**\n * Render mode for sizing\n * - fixed: width/height set from size registry (mm/in/px)\n * - dynamic: width:100% with preserved aspect ratio\n * @default 'fixed'\n */\n renderMode: \"fixed\" | \"dynamic\";\n /**\n * Default size applied when attrs.size is absent\n * @default '16x9'\n */\n defaultSize: \"16x9\" | \"4x3\" | \"a4-portrait\" | \"a4-landscape\" | \"letter-portrait\" | \"letter-landscape\" | \"linkedin-banner\";\n /**\n * Inject @media print/@page CSS for paper sizes\n * @default true\n */\n injectPrintCSS: boolean;\n /**\n * Content Security Policy nonce\n */\n injectNonce?: string;\n /**\n * Hover outline highlight for nodes within a slide.\n * - false: disabled (default)\n * - true: enabled with defaults\n * - object: enabled with overrides\n */\n hoverOutline:\n | false\n | true\n | {\n color?: string;\n width?: string;\n offset?: string;\n };\n /**\n * When enabled, hovering a container outlines its descendant blocks too.\n * @default false\n */\n hoverOutlineCascade?: boolean;\n}\n\nconst SlidePluginKey = new PluginKey(\"slide\");\n\nexport const Slide = Node.create<SlideOptions>({\n name: \"slide\",\n isolating: true,\n content: \"row+\",\n\n group: \"slide\",\n\n defining: true,\n\n addOptions() {\n return {\n HTMLAttributes: {},\n injectCSS: true,\n renderMode: \"fixed\",\n defaultSize: \"16x9\",\n injectPrintCSS: true,\n injectNonce: undefined,\n hoverOutline: false,\n hoverOutlineCascade: false,\n };\n },\n\n addAttributes() {\n return {\n size: {\n default: this.options.defaultSize,\n parseHTML: (element) => element.getAttribute(\"data-size\") || this.options.defaultSize,\n renderHTML: (attributes) => {\n if (!attributes.size) {\n return { \"data-size\": this.options.defaultSize };\n }\n return { \"data-size\": attributes.size };\n },\n },\n className: {\n default: \"\",\n },\n id: {\n default: null,\n },\n backgroundMode: {\n default: \"none\",\n },\n backgroundColor: {\n default: null,\n },\n backgroundImage: {\n default: null,\n },\n backgroundOverlayColor: {\n default: null,\n },\n backgroundOverlayOpacity: {\n default: null,\n },\n };\n },\n\n parseHTML() {\n return [{ tag: \"div.slide\" }];\n },\n\n renderHTML({ HTMLAttributes }) {\n const merged = mergeAttributes(this.options.HTMLAttributes, HTMLAttributes);\n const {\n backgroundMode,\n backgroundColor,\n backgroundImage,\n backgroundOverlayColor,\n backgroundOverlayOpacity,\n ...rest\n } = merged;\n\n const styleParts: string[] = [];\n\n const hoverEnabled = this.options.hoverOutline !== false;\n const hoverConfig =\n this.options.hoverOutline === true ? {} : (this.options.hoverOutline || {});\n const hoverCascade = !!this.options.hoverOutlineCascade;\n\n if (backgroundColor) {\n styleParts.push(`--slide-bg-color: ${backgroundColor}`);\n }\n\n if (backgroundImage) {\n const escaped = String(backgroundImage).replace(/\"/g, '\\\\\"');\n styleParts.push(`background-image: url(\"${escaped}\")`);\n }\n\n if (backgroundOverlayColor) {\n styleParts.push(`--slide-bg-overlay-color: ${backgroundOverlayColor}`);\n }\n\n if (backgroundOverlayOpacity != null) {\n styleParts.push(`--slide-bg-overlay-opacity: ${backgroundOverlayOpacity}`);\n }\n\n if (hoverEnabled) {\n const {\n color = \"rgba(59, 130, 246, 0.65)\",\n width = \"1.5px\",\n offset = \"4px\",\n } = hoverConfig;\n styleParts.push(`--slide-hover-outline-color: ${color}`);\n styleParts.push(`--slide-hover-outline-width: ${width}`);\n styleParts.push(`--slide-hover-outline-offset: ${offset}`);\n }\n\n const style = [rest.style, styleParts.join(\"; \")].filter(Boolean).join(\"; \");\n\n const className = [rest.class, rest.className, \"slide\"].filter(Boolean).join(\" \");\n\n delete (rest as any).className;\n delete (rest as any).class;\n\n return [\n \"div\",\n {\n ...rest,\n class: className || \"slide\",\n \"data-node-type\": \"slide\",\n \"data-bg-mode\": backgroundMode || \"none\",\n \"data-hover-outline\": hoverEnabled ? \"on\" : undefined,\n \"data-hover-outline-cascade\": hoverCascade ? \"on\" : undefined,\n style: style || undefined,\n },\n 0,\n ];\n },\n\n addProseMirrorPlugins() {\n return [\n new Plugin({\n key: SlidePluginKey,\n state: {\n init: () => {\n if (this.options.injectCSS && typeof document !== \"undefined\") {\n createStyleTag(slideStyles, this.options.injectNonce, \"slide\");\n const sizingCss =\n this.options.renderMode === \"dynamic\" ? dynamicSizeStyles : fixedSizeStyles;\n if (sizingCss) {\n createStyleTag(sizingCss, this.options.injectNonce, \"slide-sizes\");\n }\n if (this.options.injectPrintCSS) {\n const printCss = printSizeStyles;\n if (printCss) {\n createStyleTag(printCss, this.options.injectNonce, \"slide-print\");\n }\n }\n if (this.options.hoverOutline !== false) {\n createStyleTag(\n hoverOutlineStyles,\n this.options.injectNonce,\n \"slide-hover-outline\"\n );\n }\n }\n return {};\n },\n apply: (_tr, pluginState: Record<string, never>) => pluginState,\n },\n }),\n ];\n },\n});\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/slide.ts"],"sourcesContent":["export { Slide } from \"./slide\";\nexport type { SlideOptions } from \"./slide\";\n","import { Node, mergeAttributes, createStyleTag } from \"@blockslides/core\";\nimport { Plugin, PluginKey } from \"@blockslides/pm/state\";\n\nconst slideStyles = `\n.slide {\n position: relative;\n height: var(--slide-height, 100%);\n min-height: var(--slide-min-height, 250px);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n background-color: var(--slide-bg);\n border-radius: var(--slide-border-radius);\n box-shadow: var(--slide-shadow);\n margin-bottom: var(--slide-margin-bottom);\n}\n\n/* Background helpers (driven by data attributes + inline CSS vars) */\n.slide[data-bg-mode=\"color\"] {\n background-color: var(--slide-bg-color, var(--slide-bg));\n}\n\n.slide[data-bg-mode=\"image\"],\n.slide[data-bg-mode=\"imageOverlay\"] {\n background-size: cover;\n background-repeat: no-repeat;\n background-position: center;\n}\n\n.slide[data-bg-mode=\"imageOverlay\"]::before {\n content: \"\";\n position: absolute;\n inset: 0;\n pointer-events: none;\n background-color: var(--slide-bg-overlay-color, #000);\n opacity: var(--slide-bg-overlay-opacity, 0.35);\n}\n`;\n\nconst getFixedSizeStyles = (scale: number) => `\n.slide { --slide-scale: ${scale}; }\n.slide[data-size=\"16x9\"] { width: calc(1920px * var(--slide-scale)); height: calc(1080px * var(--slide-scale)); }\n.slide[data-size=\"4x3\"] { width: calc(1600px * var(--slide-scale)); height: calc(1200px * var(--slide-scale)); }\n.slide[data-size=\"a4-portrait\"] { width: calc(210mm * var(--slide-scale)); height: calc(297mm * var(--slide-scale)); }\n.slide[data-size=\"a4-landscape\"] { width: calc(297mm * var(--slide-scale)); height: calc(210mm * var(--slide-scale)); }\n.slide[data-size=\"letter-portrait\"] { width: calc(8.5in * var(--slide-scale)); height: calc(11in * var(--slide-scale)); }\n.slide[data-size=\"letter-landscape\"] { width: calc(11in * var(--slide-scale)); height: calc(8.5in * var(--slide-scale)); }\n.slide[data-size=\"linkedin-banner\"] { width: calc(1584px * var(--slide-scale)); height: calc(396px * var(--slide-scale)); }\n`.trim();\n\nconst dynamicSizeStyles = `\n.slide[data-size=\"16x9\"] { width: 100%; height: auto; aspect-ratio: 16 / 9; }\n.slide[data-size=\"4x3\"] { width: 100%; height: auto; aspect-ratio: 4 / 3; }\n.slide[data-size=\"a4-portrait\"] { width: 100%; height: auto; aspect-ratio: 210 / 297; }\n.slide[data-size=\"a4-landscape\"] { width: 100%; height: auto; aspect-ratio: 297 / 210; }\n.slide[data-size=\"letter-portrait\"] { width: 100%; height: auto; aspect-ratio: 8.5 / 11; }\n.slide[data-size=\"letter-landscape\"] { width: 100%; height: auto; aspect-ratio: 11 / 8.5; }\n.slide[data-size=\"linkedin-banner\"] { width: 100%; height: auto; aspect-ratio: 1584 / 396; }\n`.trim();\n\nconst printSizeStyles = `\n@media print {\n .slide[data-size=\"a4-portrait\"] { width: 210mm; height: 297mm; }\n @page { size: A4 portrait; margin: 0; }\n}\n@media print {\n .slide[data-size=\"a4-landscape\"] { width: 297mm; height: 210mm; }\n @page { size: A4 landscape; margin: 0; }\n}\n@media print {\n .slide[data-size=\"letter-portrait\"] { width: 8.5in; height: 11in; }\n @page { size: Letter portrait; margin: 0; }\n}\n@media print {\n .slide[data-size=\"letter-landscape\"] { width: 11in; height: 8.5in; }\n @page { size: Letter landscape; margin: 0; }\n}\n`.trim();\n\nconst hoverableSelector = [\n '[data-node-type]:not([data-node-type=\"slide\"])',\n \"p\",\n \"h1\",\n \"h2\",\n \"h3\",\n \"h4\",\n \"h5\",\n \"h6\",\n \"blockquote\",\n \"ul\",\n \"ol\",\n \"li\",\n \"pre\",\n \"figure\",\n \"table\",\n].join(\", \");\n\nconst hoverOutlineStyles = `\n.slide[data-hover-outline=\"on\"]\n :where(${hoverableSelector}):hover:not(:has(:hover)) {\n outline: var(--slide-hover-outline-width, 1.5px) solid var(--slide-hover-outline-color, rgba(59, 130, 246, 0.65));\n outline-offset: var(--slide-hover-outline-offset, 4px);\n transition: outline-color 120ms ease, outline-width 120ms ease;\n}\n\n/* Cascade: when hovering a container, outline its descendant blocks too */\n.slide[data-hover-outline=\"on\"][data-hover-outline-cascade=\"on\"]\n :where(${hoverableSelector}):hover\n :where(${hoverableSelector}) {\n outline: var(--slide-hover-outline-width, 1.5px) solid var(--slide-hover-outline-color, rgba(59, 130, 246, 0.65));\n outline-offset: var(--slide-hover-outline-offset, 4px);\n transition: outline-color 120ms ease, outline-width 120ms ease;\n}\n`.trim();\n\nexport interface SlideOptions {\n /**\n * The HTML attributes for a slide node.\n * @default {}\n * @example { class: 'foo' }\n */\n HTMLAttributes: Record<string, any>;\n /**\n * Whether to inject CSS styles\n * @default true\n */\n injectCSS: boolean;\n /**\n * Render mode for sizing\n * - fixed: width/height set from size registry (mm/in/px)\n * - dynamic: width:100% with preserved aspect ratio\n * @default 'fixed'\n */\n renderMode: \"fixed\" | \"dynamic\";\n /**\n * Default size applied when attrs.size is absent\n * @default '16x9'\n */\n defaultSize: \"16x9\" | \"4x3\" | \"a4-portrait\" | \"a4-landscape\" | \"letter-portrait\" | \"letter-landscape\" | \"linkedin-banner\";\n /**\n * Scale factor for fixed-size slides.\n * Only applies when renderMode is 'fixed'.\n * Helpful for shrinking/expanding fixed previews without changing size presets.\n * @default 1\n */\n scale: number;\n /**\n * Inject @media print/@page CSS for paper sizes\n * @default true\n */\n injectPrintCSS: boolean;\n /**\n * Content Security Policy nonce\n */\n injectNonce?: string;\n /**\n * Hover outline highlight for nodes within a slide.\n * - false: disabled (default)\n * - true: enabled with defaults\n * - object: enabled with overrides\n */\n hoverOutline:\n | false\n | true\n | {\n color?: string;\n width?: string;\n offset?: string;\n };\n /**\n * When enabled, hovering a container outlines its descendant blocks too.\n * @default false\n */\n hoverOutlineCascade?: boolean;\n}\n\nconst SlidePluginKey = new PluginKey(\"slide\");\n\nexport const Slide = Node.create<SlideOptions>({\n name: \"slide\",\n isolating: true,\n content: \"row+\",\n\n group: \"slide\",\n\n defining: true,\n\n addOptions() {\n return {\n HTMLAttributes: {},\n injectCSS: true,\n renderMode: \"fixed\",\n defaultSize: \"16x9\",\n scale: 1,\n injectPrintCSS: true,\n injectNonce: undefined,\n hoverOutline: false,\n hoverOutlineCascade: false,\n };\n },\n\n addAttributes() {\n return {\n size: {\n default: this.options.defaultSize,\n parseHTML: (element) => element.getAttribute(\"data-size\") || this.options.defaultSize,\n renderHTML: (attributes) => {\n if (!attributes.size) {\n return { \"data-size\": this.options.defaultSize };\n }\n return { \"data-size\": attributes.size };\n },\n },\n className: {\n default: \"\",\n },\n id: {\n default: null,\n },\n backgroundMode: {\n default: \"none\",\n },\n backgroundColor: {\n default: null,\n },\n backgroundImage: {\n default: null,\n },\n backgroundOverlayColor: {\n default: null,\n },\n backgroundOverlayOpacity: {\n default: null,\n },\n };\n },\n\n parseHTML() {\n return [{ tag: \"div.slide\" }];\n },\n\n renderHTML({ HTMLAttributes }) {\n const merged = mergeAttributes(this.options.HTMLAttributes, HTMLAttributes);\n const {\n backgroundMode,\n backgroundColor,\n backgroundImage,\n backgroundOverlayColor,\n backgroundOverlayOpacity,\n ...rest\n } = merged;\n\n const styleParts: string[] = [];\n\n const hoverEnabled = this.options.hoverOutline !== false;\n const hoverConfig =\n this.options.hoverOutline === true ? {} : (this.options.hoverOutline || {});\n const hoverCascade = !!this.options.hoverOutlineCascade;\n\n if (backgroundColor) {\n styleParts.push(`--slide-bg-color: ${backgroundColor}`);\n }\n\n if (backgroundImage) {\n const escaped = String(backgroundImage).replace(/\"/g, '\\\\\"');\n styleParts.push(`background-image: url(\"${escaped}\")`);\n }\n\n if (backgroundOverlayColor) {\n styleParts.push(`--slide-bg-overlay-color: ${backgroundOverlayColor}`);\n }\n\n if (backgroundOverlayOpacity != null) {\n styleParts.push(`--slide-bg-overlay-opacity: ${backgroundOverlayOpacity}`);\n }\n\n if (hoverEnabled) {\n const {\n color = \"rgba(59, 130, 246, 0.65)\",\n width = \"1.5px\",\n offset = \"4px\",\n } = hoverConfig;\n styleParts.push(`--slide-hover-outline-color: ${color}`);\n styleParts.push(`--slide-hover-outline-width: ${width}`);\n styleParts.push(`--slide-hover-outline-offset: ${offset}`);\n }\n\n const style = [rest.style, styleParts.join(\"; \")].filter(Boolean).join(\"; \");\n\n const className = [rest.class, rest.className, \"slide\"].filter(Boolean).join(\" \");\n\n delete (rest as any).className;\n delete (rest as any).class;\n\n return [\n \"div\",\n {\n ...rest,\n class: className || \"slide\",\n \"data-node-type\": \"slide\",\n \"data-bg-mode\": backgroundMode || \"none\",\n \"data-hover-outline\": hoverEnabled ? \"on\" : undefined,\n \"data-hover-outline-cascade\": hoverCascade ? \"on\" : undefined,\n style: style || undefined,\n },\n 0,\n ];\n },\n\n addProseMirrorPlugins() {\n return [\n new Plugin({\n key: SlidePluginKey,\n state: {\n init: () => {\n // Warn if scale is set but renderMode is not 'fixed'\n if (this.options.scale !== 1 && this.options.renderMode !== \"fixed\") {\n console.warn(\n `[Slide] The 'scale' option only applies when renderMode is 'fixed'. ` +\n `Current renderMode is '${this.options.renderMode}', so the scale value of ${this.options.scale} will be ignored.`\n );\n }\n\n if (this.options.injectCSS && typeof document !== \"undefined\") {\n createStyleTag(slideStyles, this.options.injectNonce, \"slide\");\n const sizingCss =\n this.options.renderMode === \"dynamic\"\n ? dynamicSizeStyles\n : getFixedSizeStyles(this.options.scale);\n if (sizingCss) {\n createStyleTag(sizingCss, this.options.injectNonce, \"slide-sizes\");\n }\n if (this.options.injectPrintCSS) {\n const printCss = printSizeStyles;\n if (printCss) {\n createStyleTag(printCss, this.options.injectNonce, \"slide-print\");\n }\n }\n if (this.options.hoverOutline !== false) {\n createStyleTag(\n hoverOutlineStyles,\n this.options.injectNonce,\n \"slide-hover-outline\"\n );\n }\n }\n return {};\n },\n apply: (_tr, pluginState: Record<string, never>) => pluginState,\n },\n }),\n ];\n },\n});\n"],"mappings":"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","names":[]}
package/dist/index.mjs CHANGED
@@ -36,8 +36,8 @@ var slideStyles = `
36
36
  opacity: var(--slide-bg-overlay-opacity, 0.35);
37
37
  }
38
38
  `;
39
- var fixedSizeStyles = `
40
- .slide { --slide-scale: 1; }
39
+ var getFixedSizeStyles = (scale) => `
40
+ .slide { --slide-scale: ${scale}; }
41
41
  .slide[data-size="16x9"] { width: calc(1920px * var(--slide-scale)); height: calc(1080px * var(--slide-scale)); }
42
42
  .slide[data-size="4x3"] { width: calc(1600px * var(--slide-scale)); height: calc(1200px * var(--slide-scale)); }
43
43
  .slide[data-size="a4-portrait"] { width: calc(210mm * var(--slide-scale)); height: calc(297mm * var(--slide-scale)); }
@@ -120,6 +120,7 @@ var Slide = Node.create({
120
120
  injectCSS: true,
121
121
  renderMode: "fixed",
122
122
  defaultSize: "16x9",
123
+ scale: 1,
123
124
  injectPrintCSS: true,
124
125
  injectNonce: void 0,
125
126
  hoverOutline: false,
@@ -225,9 +226,14 @@ var Slide = Node.create({
225
226
  key: SlidePluginKey,
226
227
  state: {
227
228
  init: () => {
229
+ if (this.options.scale !== 1 && this.options.renderMode !== "fixed") {
230
+ console.warn(
231
+ `[Slide] The 'scale' option only applies when renderMode is 'fixed'. Current renderMode is '${this.options.renderMode}', so the scale value of ${this.options.scale} will be ignored.`
232
+ );
233
+ }
228
234
  if (this.options.injectCSS && typeof document !== "undefined") {
229
235
  createStyleTag(slideStyles, this.options.injectNonce, "slide");
230
- const sizingCss = this.options.renderMode === "dynamic" ? dynamicSizeStyles : fixedSizeStyles;
236
+ const sizingCss = this.options.renderMode === "dynamic" ? dynamicSizeStyles : getFixedSizeStyles(this.options.scale);
231
237
  if (sizingCss) {
232
238
  createStyleTag(sizingCss, this.options.injectNonce, "slide-sizes");
233
239
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/slide.ts"],"sourcesContent":["import { Node, mergeAttributes, createStyleTag } from \"@blockslides/core\";\nimport { Plugin, PluginKey } from \"@blockslides/pm/state\";\n\nconst slideStyles = `\n.slide {\n position: relative;\n height: var(--slide-height, 100%);\n min-height: var(--slide-min-height, 250px);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n background-color: var(--slide-bg);\n border-radius: var(--slide-border-radius);\n box-shadow: var(--slide-shadow);\n margin-bottom: var(--slide-margin-bottom);\n}\n\n/* Background helpers (driven by data attributes + inline CSS vars) */\n.slide[data-bg-mode=\"color\"] {\n background-color: var(--slide-bg-color, var(--slide-bg));\n}\n\n.slide[data-bg-mode=\"image\"],\n.slide[data-bg-mode=\"imageOverlay\"] {\n background-size: cover;\n background-repeat: no-repeat;\n background-position: center;\n}\n\n.slide[data-bg-mode=\"imageOverlay\"]::before {\n content: \"\";\n position: absolute;\n inset: 0;\n pointer-events: none;\n background-color: var(--slide-bg-overlay-color, #000);\n opacity: var(--slide-bg-overlay-opacity, 0.35);\n}\n`;\n\nconst fixedSizeStyles = `\n.slide { --slide-scale: 1; }\n.slide[data-size=\"16x9\"] { width: calc(1920px * var(--slide-scale)); height: calc(1080px * var(--slide-scale)); }\n.slide[data-size=\"4x3\"] { width: calc(1600px * var(--slide-scale)); height: calc(1200px * var(--slide-scale)); }\n.slide[data-size=\"a4-portrait\"] { width: calc(210mm * var(--slide-scale)); height: calc(297mm * var(--slide-scale)); }\n.slide[data-size=\"a4-landscape\"] { width: calc(297mm * var(--slide-scale)); height: calc(210mm * var(--slide-scale)); }\n.slide[data-size=\"letter-portrait\"] { width: calc(8.5in * var(--slide-scale)); height: calc(11in * var(--slide-scale)); }\n.slide[data-size=\"letter-landscape\"] { width: calc(11in * var(--slide-scale)); height: calc(8.5in * var(--slide-scale)); }\n.slide[data-size=\"linkedin-banner\"] { width: calc(1584px * var(--slide-scale)); height: calc(396px * var(--slide-scale)); }\n`.trim();\n\nconst dynamicSizeStyles = `\n.slide[data-size=\"16x9\"] { width: 100%; height: auto; aspect-ratio: 16 / 9; }\n.slide[data-size=\"4x3\"] { width: 100%; height: auto; aspect-ratio: 4 / 3; }\n.slide[data-size=\"a4-portrait\"] { width: 100%; height: auto; aspect-ratio: 210 / 297; }\n.slide[data-size=\"a4-landscape\"] { width: 100%; height: auto; aspect-ratio: 297 / 210; }\n.slide[data-size=\"letter-portrait\"] { width: 100%; height: auto; aspect-ratio: 8.5 / 11; }\n.slide[data-size=\"letter-landscape\"] { width: 100%; height: auto; aspect-ratio: 11 / 8.5; }\n.slide[data-size=\"linkedin-banner\"] { width: 100%; height: auto; aspect-ratio: 1584 / 396; }\n`.trim();\n\nconst printSizeStyles = `\n@media print {\n .slide[data-size=\"a4-portrait\"] { width: 210mm; height: 297mm; }\n @page { size: A4 portrait; margin: 0; }\n}\n@media print {\n .slide[data-size=\"a4-landscape\"] { width: 297mm; height: 210mm; }\n @page { size: A4 landscape; margin: 0; }\n}\n@media print {\n .slide[data-size=\"letter-portrait\"] { width: 8.5in; height: 11in; }\n @page { size: Letter portrait; margin: 0; }\n}\n@media print {\n .slide[data-size=\"letter-landscape\"] { width: 11in; height: 8.5in; }\n @page { size: Letter landscape; margin: 0; }\n}\n`.trim();\n\nconst hoverableSelector = [\n '[data-node-type]:not([data-node-type=\"slide\"])',\n \"p\",\n \"h1\",\n \"h2\",\n \"h3\",\n \"h4\",\n \"h5\",\n \"h6\",\n \"blockquote\",\n \"ul\",\n \"ol\",\n \"li\",\n \"pre\",\n \"figure\",\n \"table\",\n].join(\", \");\n\nconst hoverOutlineStyles = `\n.slide[data-hover-outline=\"on\"]\n :where(${hoverableSelector}):hover:not(:has(:hover)) {\n outline: var(--slide-hover-outline-width, 1.5px) solid var(--slide-hover-outline-color, rgba(59, 130, 246, 0.65));\n outline-offset: var(--slide-hover-outline-offset, 4px);\n transition: outline-color 120ms ease, outline-width 120ms ease;\n}\n\n/* Cascade: when hovering a container, outline its descendant blocks too */\n.slide[data-hover-outline=\"on\"][data-hover-outline-cascade=\"on\"]\n :where(${hoverableSelector}):hover\n :where(${hoverableSelector}) {\n outline: var(--slide-hover-outline-width, 1.5px) solid var(--slide-hover-outline-color, rgba(59, 130, 246, 0.65));\n outline-offset: var(--slide-hover-outline-offset, 4px);\n transition: outline-color 120ms ease, outline-width 120ms ease;\n}\n`.trim();\n\nexport interface SlideOptions {\n /**\n * The HTML attributes for a slide node.\n * @default {}\n * @example { class: 'foo' }\n */\n HTMLAttributes: Record<string, any>;\n /**\n * Whether to inject CSS styles\n * @default true\n */\n injectCSS: boolean;\n /**\n * Render mode for sizing\n * - fixed: width/height set from size registry (mm/in/px)\n * - dynamic: width:100% with preserved aspect ratio\n * @default 'fixed'\n */\n renderMode: \"fixed\" | \"dynamic\";\n /**\n * Default size applied when attrs.size is absent\n * @default '16x9'\n */\n defaultSize: \"16x9\" | \"4x3\" | \"a4-portrait\" | \"a4-landscape\" | \"letter-portrait\" | \"letter-landscape\" | \"linkedin-banner\";\n /**\n * Inject @media print/@page CSS for paper sizes\n * @default true\n */\n injectPrintCSS: boolean;\n /**\n * Content Security Policy nonce\n */\n injectNonce?: string;\n /**\n * Hover outline highlight for nodes within a slide.\n * - false: disabled (default)\n * - true: enabled with defaults\n * - object: enabled with overrides\n */\n hoverOutline:\n | false\n | true\n | {\n color?: string;\n width?: string;\n offset?: string;\n };\n /**\n * When enabled, hovering a container outlines its descendant blocks too.\n * @default false\n */\n hoverOutlineCascade?: boolean;\n}\n\nconst SlidePluginKey = new PluginKey(\"slide\");\n\nexport const Slide = Node.create<SlideOptions>({\n name: \"slide\",\n isolating: true,\n content: \"row+\",\n\n group: \"slide\",\n\n defining: true,\n\n addOptions() {\n return {\n HTMLAttributes: {},\n injectCSS: true,\n renderMode: \"fixed\",\n defaultSize: \"16x9\",\n injectPrintCSS: true,\n injectNonce: undefined,\n hoverOutline: false,\n hoverOutlineCascade: false,\n };\n },\n\n addAttributes() {\n return {\n size: {\n default: this.options.defaultSize,\n parseHTML: (element) => element.getAttribute(\"data-size\") || this.options.defaultSize,\n renderHTML: (attributes) => {\n if (!attributes.size) {\n return { \"data-size\": this.options.defaultSize };\n }\n return { \"data-size\": attributes.size };\n },\n },\n className: {\n default: \"\",\n },\n id: {\n default: null,\n },\n backgroundMode: {\n default: \"none\",\n },\n backgroundColor: {\n default: null,\n },\n backgroundImage: {\n default: null,\n },\n backgroundOverlayColor: {\n default: null,\n },\n backgroundOverlayOpacity: {\n default: null,\n },\n };\n },\n\n parseHTML() {\n return [{ tag: \"div.slide\" }];\n },\n\n renderHTML({ HTMLAttributes }) {\n const merged = mergeAttributes(this.options.HTMLAttributes, HTMLAttributes);\n const {\n backgroundMode,\n backgroundColor,\n backgroundImage,\n backgroundOverlayColor,\n backgroundOverlayOpacity,\n ...rest\n } = merged;\n\n const styleParts: string[] = [];\n\n const hoverEnabled = this.options.hoverOutline !== false;\n const hoverConfig =\n this.options.hoverOutline === true ? {} : (this.options.hoverOutline || {});\n const hoverCascade = !!this.options.hoverOutlineCascade;\n\n if (backgroundColor) {\n styleParts.push(`--slide-bg-color: ${backgroundColor}`);\n }\n\n if (backgroundImage) {\n const escaped = String(backgroundImage).replace(/\"/g, '\\\\\"');\n styleParts.push(`background-image: url(\"${escaped}\")`);\n }\n\n if (backgroundOverlayColor) {\n styleParts.push(`--slide-bg-overlay-color: ${backgroundOverlayColor}`);\n }\n\n if (backgroundOverlayOpacity != null) {\n styleParts.push(`--slide-bg-overlay-opacity: ${backgroundOverlayOpacity}`);\n }\n\n if (hoverEnabled) {\n const {\n color = \"rgba(59, 130, 246, 0.65)\",\n width = \"1.5px\",\n offset = \"4px\",\n } = hoverConfig;\n styleParts.push(`--slide-hover-outline-color: ${color}`);\n styleParts.push(`--slide-hover-outline-width: ${width}`);\n styleParts.push(`--slide-hover-outline-offset: ${offset}`);\n }\n\n const style = [rest.style, styleParts.join(\"; \")].filter(Boolean).join(\"; \");\n\n const className = [rest.class, rest.className, \"slide\"].filter(Boolean).join(\" \");\n\n delete (rest as any).className;\n delete (rest as any).class;\n\n return [\n \"div\",\n {\n ...rest,\n class: className || \"slide\",\n \"data-node-type\": \"slide\",\n \"data-bg-mode\": backgroundMode || \"none\",\n \"data-hover-outline\": hoverEnabled ? \"on\" : undefined,\n \"data-hover-outline-cascade\": hoverCascade ? \"on\" : undefined,\n style: style || undefined,\n },\n 0,\n ];\n },\n\n addProseMirrorPlugins() {\n return [\n new Plugin({\n key: SlidePluginKey,\n state: {\n init: () => {\n if (this.options.injectCSS && typeof document !== \"undefined\") {\n createStyleTag(slideStyles, this.options.injectNonce, \"slide\");\n const sizingCss =\n this.options.renderMode === \"dynamic\" ? dynamicSizeStyles : fixedSizeStyles;\n if (sizingCss) {\n createStyleTag(sizingCss, this.options.injectNonce, \"slide-sizes\");\n }\n if (this.options.injectPrintCSS) {\n const printCss = printSizeStyles;\n if (printCss) {\n createStyleTag(printCss, this.options.injectNonce, \"slide-print\");\n }\n }\n if (this.options.hoverOutline !== false) {\n createStyleTag(\n hoverOutlineStyles,\n this.options.injectNonce,\n \"slide-hover-outline\"\n );\n }\n }\n return {};\n },\n apply: (_tr, pluginState: Record<string, never>) => pluginState,\n },\n }),\n ];\n },\n});\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/slide.ts"],"sourcesContent":["import { Node, mergeAttributes, createStyleTag } from \"@blockslides/core\";\nimport { Plugin, PluginKey } from \"@blockslides/pm/state\";\n\nconst slideStyles = `\n.slide {\n position: relative;\n height: var(--slide-height, 100%);\n min-height: var(--slide-min-height, 250px);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n background-color: var(--slide-bg);\n border-radius: var(--slide-border-radius);\n box-shadow: var(--slide-shadow);\n margin-bottom: var(--slide-margin-bottom);\n}\n\n/* Background helpers (driven by data attributes + inline CSS vars) */\n.slide[data-bg-mode=\"color\"] {\n background-color: var(--slide-bg-color, var(--slide-bg));\n}\n\n.slide[data-bg-mode=\"image\"],\n.slide[data-bg-mode=\"imageOverlay\"] {\n background-size: cover;\n background-repeat: no-repeat;\n background-position: center;\n}\n\n.slide[data-bg-mode=\"imageOverlay\"]::before {\n content: \"\";\n position: absolute;\n inset: 0;\n pointer-events: none;\n background-color: var(--slide-bg-overlay-color, #000);\n opacity: var(--slide-bg-overlay-opacity, 0.35);\n}\n`;\n\nconst getFixedSizeStyles = (scale: number) => `\n.slide { --slide-scale: ${scale}; }\n.slide[data-size=\"16x9\"] { width: calc(1920px * var(--slide-scale)); height: calc(1080px * var(--slide-scale)); }\n.slide[data-size=\"4x3\"] { width: calc(1600px * var(--slide-scale)); height: calc(1200px * var(--slide-scale)); }\n.slide[data-size=\"a4-portrait\"] { width: calc(210mm * var(--slide-scale)); height: calc(297mm * var(--slide-scale)); }\n.slide[data-size=\"a4-landscape\"] { width: calc(297mm * var(--slide-scale)); height: calc(210mm * var(--slide-scale)); }\n.slide[data-size=\"letter-portrait\"] { width: calc(8.5in * var(--slide-scale)); height: calc(11in * var(--slide-scale)); }\n.slide[data-size=\"letter-landscape\"] { width: calc(11in * var(--slide-scale)); height: calc(8.5in * var(--slide-scale)); }\n.slide[data-size=\"linkedin-banner\"] { width: calc(1584px * var(--slide-scale)); height: calc(396px * var(--slide-scale)); }\n`.trim();\n\nconst dynamicSizeStyles = `\n.slide[data-size=\"16x9\"] { width: 100%; height: auto; aspect-ratio: 16 / 9; }\n.slide[data-size=\"4x3\"] { width: 100%; height: auto; aspect-ratio: 4 / 3; }\n.slide[data-size=\"a4-portrait\"] { width: 100%; height: auto; aspect-ratio: 210 / 297; }\n.slide[data-size=\"a4-landscape\"] { width: 100%; height: auto; aspect-ratio: 297 / 210; }\n.slide[data-size=\"letter-portrait\"] { width: 100%; height: auto; aspect-ratio: 8.5 / 11; }\n.slide[data-size=\"letter-landscape\"] { width: 100%; height: auto; aspect-ratio: 11 / 8.5; }\n.slide[data-size=\"linkedin-banner\"] { width: 100%; height: auto; aspect-ratio: 1584 / 396; }\n`.trim();\n\nconst printSizeStyles = `\n@media print {\n .slide[data-size=\"a4-portrait\"] { width: 210mm; height: 297mm; }\n @page { size: A4 portrait; margin: 0; }\n}\n@media print {\n .slide[data-size=\"a4-landscape\"] { width: 297mm; height: 210mm; }\n @page { size: A4 landscape; margin: 0; }\n}\n@media print {\n .slide[data-size=\"letter-portrait\"] { width: 8.5in; height: 11in; }\n @page { size: Letter portrait; margin: 0; }\n}\n@media print {\n .slide[data-size=\"letter-landscape\"] { width: 11in; height: 8.5in; }\n @page { size: Letter landscape; margin: 0; }\n}\n`.trim();\n\nconst hoverableSelector = [\n '[data-node-type]:not([data-node-type=\"slide\"])',\n \"p\",\n \"h1\",\n \"h2\",\n \"h3\",\n \"h4\",\n \"h5\",\n \"h6\",\n \"blockquote\",\n \"ul\",\n \"ol\",\n \"li\",\n \"pre\",\n \"figure\",\n \"table\",\n].join(\", \");\n\nconst hoverOutlineStyles = `\n.slide[data-hover-outline=\"on\"]\n :where(${hoverableSelector}):hover:not(:has(:hover)) {\n outline: var(--slide-hover-outline-width, 1.5px) solid var(--slide-hover-outline-color, rgba(59, 130, 246, 0.65));\n outline-offset: var(--slide-hover-outline-offset, 4px);\n transition: outline-color 120ms ease, outline-width 120ms ease;\n}\n\n/* Cascade: when hovering a container, outline its descendant blocks too */\n.slide[data-hover-outline=\"on\"][data-hover-outline-cascade=\"on\"]\n :where(${hoverableSelector}):hover\n :where(${hoverableSelector}) {\n outline: var(--slide-hover-outline-width, 1.5px) solid var(--slide-hover-outline-color, rgba(59, 130, 246, 0.65));\n outline-offset: var(--slide-hover-outline-offset, 4px);\n transition: outline-color 120ms ease, outline-width 120ms ease;\n}\n`.trim();\n\nexport interface SlideOptions {\n /**\n * The HTML attributes for a slide node.\n * @default {}\n * @example { class: 'foo' }\n */\n HTMLAttributes: Record<string, any>;\n /**\n * Whether to inject CSS styles\n * @default true\n */\n injectCSS: boolean;\n /**\n * Render mode for sizing\n * - fixed: width/height set from size registry (mm/in/px)\n * - dynamic: width:100% with preserved aspect ratio\n * @default 'fixed'\n */\n renderMode: \"fixed\" | \"dynamic\";\n /**\n * Default size applied when attrs.size is absent\n * @default '16x9'\n */\n defaultSize: \"16x9\" | \"4x3\" | \"a4-portrait\" | \"a4-landscape\" | \"letter-portrait\" | \"letter-landscape\" | \"linkedin-banner\";\n /**\n * Scale factor for fixed-size slides.\n * Only applies when renderMode is 'fixed'.\n * Helpful for shrinking/expanding fixed previews without changing size presets.\n * @default 1\n */\n scale: number;\n /**\n * Inject @media print/@page CSS for paper sizes\n * @default true\n */\n injectPrintCSS: boolean;\n /**\n * Content Security Policy nonce\n */\n injectNonce?: string;\n /**\n * Hover outline highlight for nodes within a slide.\n * - false: disabled (default)\n * - true: enabled with defaults\n * - object: enabled with overrides\n */\n hoverOutline:\n | false\n | true\n | {\n color?: string;\n width?: string;\n offset?: string;\n };\n /**\n * When enabled, hovering a container outlines its descendant blocks too.\n * @default false\n */\n hoverOutlineCascade?: boolean;\n}\n\nconst SlidePluginKey = new PluginKey(\"slide\");\n\nexport const Slide = Node.create<SlideOptions>({\n name: \"slide\",\n isolating: true,\n content: \"row+\",\n\n group: \"slide\",\n\n defining: true,\n\n addOptions() {\n return {\n HTMLAttributes: {},\n injectCSS: true,\n renderMode: \"fixed\",\n defaultSize: \"16x9\",\n scale: 1,\n injectPrintCSS: true,\n injectNonce: undefined,\n hoverOutline: false,\n hoverOutlineCascade: false,\n };\n },\n\n addAttributes() {\n return {\n size: {\n default: this.options.defaultSize,\n parseHTML: (element) => element.getAttribute(\"data-size\") || this.options.defaultSize,\n renderHTML: (attributes) => {\n if (!attributes.size) {\n return { \"data-size\": this.options.defaultSize };\n }\n return { \"data-size\": attributes.size };\n },\n },\n className: {\n default: \"\",\n },\n id: {\n default: null,\n },\n backgroundMode: {\n default: \"none\",\n },\n backgroundColor: {\n default: null,\n },\n backgroundImage: {\n default: null,\n },\n backgroundOverlayColor: {\n default: null,\n },\n backgroundOverlayOpacity: {\n default: null,\n },\n };\n },\n\n parseHTML() {\n return [{ tag: \"div.slide\" }];\n },\n\n renderHTML({ HTMLAttributes }) {\n const merged = mergeAttributes(this.options.HTMLAttributes, HTMLAttributes);\n const {\n backgroundMode,\n backgroundColor,\n backgroundImage,\n backgroundOverlayColor,\n backgroundOverlayOpacity,\n ...rest\n } = merged;\n\n const styleParts: string[] = [];\n\n const hoverEnabled = this.options.hoverOutline !== false;\n const hoverConfig =\n this.options.hoverOutline === true ? {} : (this.options.hoverOutline || {});\n const hoverCascade = !!this.options.hoverOutlineCascade;\n\n if (backgroundColor) {\n styleParts.push(`--slide-bg-color: ${backgroundColor}`);\n }\n\n if (backgroundImage) {\n const escaped = String(backgroundImage).replace(/\"/g, '\\\\\"');\n styleParts.push(`background-image: url(\"${escaped}\")`);\n }\n\n if (backgroundOverlayColor) {\n styleParts.push(`--slide-bg-overlay-color: ${backgroundOverlayColor}`);\n }\n\n if (backgroundOverlayOpacity != null) {\n styleParts.push(`--slide-bg-overlay-opacity: ${backgroundOverlayOpacity}`);\n }\n\n if (hoverEnabled) {\n const {\n color = \"rgba(59, 130, 246, 0.65)\",\n width = \"1.5px\",\n offset = \"4px\",\n } = hoverConfig;\n styleParts.push(`--slide-hover-outline-color: ${color}`);\n styleParts.push(`--slide-hover-outline-width: ${width}`);\n styleParts.push(`--slide-hover-outline-offset: ${offset}`);\n }\n\n const style = [rest.style, styleParts.join(\"; \")].filter(Boolean).join(\"; \");\n\n const className = [rest.class, rest.className, \"slide\"].filter(Boolean).join(\" \");\n\n delete (rest as any).className;\n delete (rest as any).class;\n\n return [\n \"div\",\n {\n ...rest,\n class: className || \"slide\",\n \"data-node-type\": \"slide\",\n \"data-bg-mode\": backgroundMode || \"none\",\n \"data-hover-outline\": hoverEnabled ? \"on\" : undefined,\n \"data-hover-outline-cascade\": hoverCascade ? \"on\" : undefined,\n style: style || undefined,\n },\n 0,\n ];\n },\n\n addProseMirrorPlugins() {\n return [\n new Plugin({\n key: SlidePluginKey,\n state: {\n init: () => {\n // Warn if scale is set but renderMode is not 'fixed'\n if (this.options.scale !== 1 && this.options.renderMode !== \"fixed\") {\n console.warn(\n `[Slide] The 'scale' option only applies when renderMode is 'fixed'. ` +\n `Current renderMode is '${this.options.renderMode}', so the scale value of ${this.options.scale} will be ignored.`\n );\n }\n\n if (this.options.injectCSS && typeof document !== \"undefined\") {\n createStyleTag(slideStyles, this.options.injectNonce, \"slide\");\n const sizingCss =\n this.options.renderMode === \"dynamic\"\n ? dynamicSizeStyles\n : getFixedSizeStyles(this.options.scale);\n if (sizingCss) {\n createStyleTag(sizingCss, this.options.injectNonce, \"slide-sizes\");\n }\n if (this.options.injectPrintCSS) {\n const printCss = printSizeStyles;\n if (printCss) {\n createStyleTag(printCss, this.options.injectNonce, \"slide-print\");\n }\n }\n if (this.options.hoverOutline !== false) {\n createStyleTag(\n hoverOutlineStyles,\n this.options.injectNonce,\n \"slide-hover-outline\"\n );\n }\n }\n return {};\n },\n apply: (_tr, pluginState: Record<string, never>) => pluginState,\n },\n }),\n ];\n },\n});\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockslides/extension-slide",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Slide node extension for BlockSlides slide editor",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
package/src/slide.ts CHANGED
@@ -37,8 +37,8 @@ const slideStyles = `
37
37
  }
38
38
  `;
39
39
 
40
- const fixedSizeStyles = `
41
- .slide { --slide-scale: 1; }
40
+ const getFixedSizeStyles = (scale: number) => `
41
+ .slide { --slide-scale: ${scale}; }
42
42
  .slide[data-size="16x9"] { width: calc(1920px * var(--slide-scale)); height: calc(1080px * var(--slide-scale)); }
43
43
  .slide[data-size="4x3"] { width: calc(1600px * var(--slide-scale)); height: calc(1200px * var(--slide-scale)); }
44
44
  .slide[data-size="a4-portrait"] { width: calc(210mm * var(--slide-scale)); height: calc(297mm * var(--slide-scale)); }
@@ -137,6 +137,13 @@ export interface SlideOptions {
137
137
  * @default '16x9'
138
138
  */
139
139
  defaultSize: "16x9" | "4x3" | "a4-portrait" | "a4-landscape" | "letter-portrait" | "letter-landscape" | "linkedin-banner";
140
+ /**
141
+ * Scale factor for fixed-size slides.
142
+ * Only applies when renderMode is 'fixed'.
143
+ * Helpful for shrinking/expanding fixed previews without changing size presets.
144
+ * @default 1
145
+ */
146
+ scale: number;
140
147
  /**
141
148
  * Inject @media print/@page CSS for paper sizes
142
149
  * @default true
@@ -184,6 +191,7 @@ export const Slide = Node.create<SlideOptions>({
184
191
  injectCSS: true,
185
192
  renderMode: "fixed",
186
193
  defaultSize: "16x9",
194
+ scale: 1,
187
195
  injectPrintCSS: true,
188
196
  injectNonce: undefined,
189
197
  hoverOutline: false,
@@ -305,10 +313,20 @@ export const Slide = Node.create<SlideOptions>({
305
313
  key: SlidePluginKey,
306
314
  state: {
307
315
  init: () => {
316
+ // Warn if scale is set but renderMode is not 'fixed'
317
+ if (this.options.scale !== 1 && this.options.renderMode !== "fixed") {
318
+ console.warn(
319
+ `[Slide] The 'scale' option only applies when renderMode is 'fixed'. ` +
320
+ `Current renderMode is '${this.options.renderMode}', so the scale value of ${this.options.scale} will be ignored.`
321
+ );
322
+ }
323
+
308
324
  if (this.options.injectCSS && typeof document !== "undefined") {
309
325
  createStyleTag(slideStyles, this.options.injectNonce, "slide");
310
326
  const sizingCss =
311
- this.options.renderMode === "dynamic" ? dynamicSizeStyles : fixedSizeStyles;
327
+ this.options.renderMode === "dynamic"
328
+ ? dynamicSizeStyles
329
+ : getFixedSizeStyles(this.options.scale);
312
330
  if (sizingCss) {
313
331
  createStyleTag(sizingCss, this.options.injectNonce, "slide-sizes");
314
332
  }