@blockslides/extension-slide 0.2.1 → 0.3.1

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,12 @@ 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
+ * @default 1
31
+ */
32
+ scale: number;
27
33
  /**
28
34
  * Inject @media print/@page CSS for paper sizes
29
35
  * @default true
@@ -33,6 +39,22 @@ interface SlideOptions {
33
39
  * Content Security Policy nonce
34
40
  */
35
41
  injectNonce?: string;
42
+ /**
43
+ * Hover outline highlight for nodes within a slide.
44
+ * - false: disabled (default)
45
+ * - true: enabled with defaults
46
+ * - object: enabled with overrides
47
+ */
48
+ hoverOutline: false | true | {
49
+ color?: string;
50
+ width?: string;
51
+ offset?: string;
52
+ };
53
+ /**
54
+ * When enabled, hovering a container outlines its descendant blocks too.
55
+ * @default false
56
+ */
57
+ hoverOutlineCascade?: boolean;
36
58
  }
37
59
  declare const Slide: Node<SlideOptions, any>;
38
60
 
package/dist/index.d.ts CHANGED
@@ -24,6 +24,12 @@ 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
+ * @default 1
31
+ */
32
+ scale: number;
27
33
  /**
28
34
  * Inject @media print/@page CSS for paper sizes
29
35
  * @default true
@@ -33,6 +39,22 @@ interface SlideOptions {
33
39
  * Content Security Policy nonce
34
40
  */
35
41
  injectNonce?: string;
42
+ /**
43
+ * Hover outline highlight for nodes within a slide.
44
+ * - false: disabled (default)
45
+ * - true: enabled with defaults
46
+ * - object: enabled with overrides
47
+ */
48
+ hoverOutline: false | true | {
49
+ color?: string;
50
+ width?: string;
51
+ offset?: string;
52
+ };
53
+ /**
54
+ * When enabled, hovering a container outlines its descendant blocks too.
55
+ * @default false
56
+ */
57
+ hoverOutlineCascade?: boolean;
36
58
  }
37
59
  declare const Slide: Node<SlideOptions, any>;
38
60
 
package/dist/index.js CHANGED
@@ -62,14 +62,15 @@ var slideStyles = `
62
62
  opacity: var(--slide-bg-overlay-opacity, 0.35);
63
63
  }
64
64
  `;
65
- var fixedSizeStyles = `
66
- .slide[data-size="16x9"] { width: 1920px; height: 1080px; }
67
- .slide[data-size="4x3"] { width: 1600px; height: 1200px; }
68
- .slide[data-size="a4-portrait"] { width: 210mm; height: 297mm; }
69
- .slide[data-size="a4-landscape"] { width: 297mm; height: 210mm; }
70
- .slide[data-size="letter-portrait"] { width: 8.5in; height: 11in; }
71
- .slide[data-size="letter-landscape"] { width: 11in; height: 8.5in; }
72
- .slide[data-size="linkedin-banner"] { width: 1584px; height: 396px; }
65
+ var getFixedSizeStyles = (scale) => `
66
+ .slide { --slide-scale: ${scale}; }
67
+ .slide[data-size="16x9"] { width: calc(1920px * var(--slide-scale)); height: calc(1080px * var(--slide-scale)); }
68
+ .slide[data-size="4x3"] { width: calc(1600px * var(--slide-scale)); height: calc(1200px * var(--slide-scale)); }
69
+ .slide[data-size="a4-portrait"] { width: calc(210mm * var(--slide-scale)); height: calc(297mm * var(--slide-scale)); }
70
+ .slide[data-size="a4-landscape"] { width: calc(297mm * var(--slide-scale)); height: calc(210mm * var(--slide-scale)); }
71
+ .slide[data-size="letter-portrait"] { width: calc(8.5in * var(--slide-scale)); height: calc(11in * var(--slide-scale)); }
72
+ .slide[data-size="letter-landscape"] { width: calc(11in * var(--slide-scale)); height: calc(8.5in * var(--slide-scale)); }
73
+ .slide[data-size="linkedin-banner"] { width: calc(1584px * var(--slide-scale)); height: calc(396px * var(--slide-scale)); }
73
74
  `.trim();
74
75
  var dynamicSizeStyles = `
75
76
  .slide[data-size="16x9"] { width: 100%; height: auto; aspect-ratio: 16 / 9; }
@@ -98,6 +99,40 @@ var printSizeStyles = `
98
99
  @page { size: Letter landscape; margin: 0; }
99
100
  }
100
101
  `.trim();
102
+ var hoverableSelector = [
103
+ '[data-node-type]:not([data-node-type="slide"])',
104
+ "p",
105
+ "h1",
106
+ "h2",
107
+ "h3",
108
+ "h4",
109
+ "h5",
110
+ "h6",
111
+ "blockquote",
112
+ "ul",
113
+ "ol",
114
+ "li",
115
+ "pre",
116
+ "figure",
117
+ "table"
118
+ ].join(", ");
119
+ var hoverOutlineStyles = `
120
+ .slide[data-hover-outline="on"]
121
+ :where(${hoverableSelector}):hover:not(:has(:hover)) {
122
+ outline: var(--slide-hover-outline-width, 1.5px) solid var(--slide-hover-outline-color, rgba(59, 130, 246, 0.65));
123
+ outline-offset: var(--slide-hover-outline-offset, 4px);
124
+ transition: outline-color 120ms ease, outline-width 120ms ease;
125
+ }
126
+
127
+ /* Cascade: when hovering a container, outline its descendant blocks too */
128
+ .slide[data-hover-outline="on"][data-hover-outline-cascade="on"]
129
+ :where(${hoverableSelector}):hover
130
+ :where(${hoverableSelector}) {
131
+ outline: var(--slide-hover-outline-width, 1.5px) solid var(--slide-hover-outline-color, rgba(59, 130, 246, 0.65));
132
+ outline-offset: var(--slide-hover-outline-offset, 4px);
133
+ transition: outline-color 120ms ease, outline-width 120ms ease;
134
+ }
135
+ `.trim();
101
136
  var SlidePluginKey = new import_state.PluginKey("slide");
102
137
  var Slide = import_core.Node.create({
103
138
  name: "slide",
@@ -111,8 +146,11 @@ var Slide = import_core.Node.create({
111
146
  injectCSS: true,
112
147
  renderMode: "fixed",
113
148
  defaultSize: "16x9",
149
+ scale: 1,
114
150
  injectPrintCSS: true,
115
- injectNonce: void 0
151
+ injectNonce: void 0,
152
+ hoverOutline: false,
153
+ hoverOutlineCascade: false
116
154
  };
117
155
  },
118
156
  addAttributes() {
@@ -164,6 +202,9 @@ var Slide = import_core.Node.create({
164
202
  ...rest
165
203
  } = merged;
166
204
  const styleParts = [];
205
+ const hoverEnabled = this.options.hoverOutline !== false;
206
+ const hoverConfig = this.options.hoverOutline === true ? {} : this.options.hoverOutline || {};
207
+ const hoverCascade = !!this.options.hoverOutlineCascade;
167
208
  if (backgroundColor) {
168
209
  styleParts.push(`--slide-bg-color: ${backgroundColor}`);
169
210
  }
@@ -177,6 +218,16 @@ var Slide = import_core.Node.create({
177
218
  if (backgroundOverlayOpacity != null) {
178
219
  styleParts.push(`--slide-bg-overlay-opacity: ${backgroundOverlayOpacity}`);
179
220
  }
221
+ if (hoverEnabled) {
222
+ const {
223
+ color = "rgba(59, 130, 246, 0.65)",
224
+ width = "1.5px",
225
+ offset = "4px"
226
+ } = hoverConfig;
227
+ styleParts.push(`--slide-hover-outline-color: ${color}`);
228
+ styleParts.push(`--slide-hover-outline-width: ${width}`);
229
+ styleParts.push(`--slide-hover-outline-offset: ${offset}`);
230
+ }
180
231
  const style = [rest.style, styleParts.join("; ")].filter(Boolean).join("; ");
181
232
  const className = [rest.class, rest.className, "slide"].filter(Boolean).join(" ");
182
233
  delete rest.className;
@@ -188,6 +239,8 @@ var Slide = import_core.Node.create({
188
239
  class: className || "slide",
189
240
  "data-node-type": "slide",
190
241
  "data-bg-mode": backgroundMode || "none",
242
+ "data-hover-outline": hoverEnabled ? "on" : void 0,
243
+ "data-hover-outline-cascade": hoverCascade ? "on" : void 0,
191
244
  style: style || void 0
192
245
  },
193
246
  0
@@ -199,9 +252,14 @@ var Slide = import_core.Node.create({
199
252
  key: SlidePluginKey,
200
253
  state: {
201
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
+ }
202
260
  if (this.options.injectCSS && typeof document !== "undefined") {
203
261
  (0, import_core.createStyleTag)(slideStyles, this.options.injectNonce, "slide");
204
- const sizingCss = this.options.renderMode === "dynamic" ? dynamicSizeStyles : fixedSizeStyles;
262
+ const sizingCss = this.options.renderMode === "dynamic" ? dynamicSizeStyles : getFixedSizeStyles(this.options.scale);
205
263
  if (sizingCss) {
206
264
  (0, import_core.createStyleTag)(sizingCss, this.options.injectNonce, "slide-sizes");
207
265
  }
@@ -211,6 +269,13 @@ var Slide = import_core.Node.create({
211
269
  (0, import_core.createStyleTag)(printCss, this.options.injectNonce, "slide-print");
212
270
  }
213
271
  }
272
+ if (this.options.hoverOutline !== false) {
273
+ (0, import_core.createStyleTag)(
274
+ hoverOutlineStyles,
275
+ this.options.injectNonce,
276
+ "slide-hover-outline"
277
+ );
278
+ }
214
279
  }
215
280
  return {};
216
281
  },
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[data-size=\"16x9\"] { width: 1920px; height: 1080px; }\n.slide[data-size=\"4x3\"] { width: 1600px; height: 1200px; }\n.slide[data-size=\"a4-portrait\"] { width: 210mm; height: 297mm; }\n.slide[data-size=\"a4-landscape\"] { width: 297mm; height: 210mm; }\n.slide[data-size=\"letter-portrait\"] { width: 8.5in; height: 11in; }\n.slide[data-size=\"letter-landscape\"] { width: 11in; height: 8.5in; }\n.slide[data-size=\"linkedin-banner\"] { width: 1584px; height: 396px; }\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\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\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 };\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 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 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 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 }\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 * @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,14 +36,15 @@ var slideStyles = `
36
36
  opacity: var(--slide-bg-overlay-opacity, 0.35);
37
37
  }
38
38
  `;
39
- var fixedSizeStyles = `
40
- .slide[data-size="16x9"] { width: 1920px; height: 1080px; }
41
- .slide[data-size="4x3"] { width: 1600px; height: 1200px; }
42
- .slide[data-size="a4-portrait"] { width: 210mm; height: 297mm; }
43
- .slide[data-size="a4-landscape"] { width: 297mm; height: 210mm; }
44
- .slide[data-size="letter-portrait"] { width: 8.5in; height: 11in; }
45
- .slide[data-size="letter-landscape"] { width: 11in; height: 8.5in; }
46
- .slide[data-size="linkedin-banner"] { width: 1584px; height: 396px; }
39
+ var getFixedSizeStyles = (scale) => `
40
+ .slide { --slide-scale: ${scale}; }
41
+ .slide[data-size="16x9"] { width: calc(1920px * var(--slide-scale)); height: calc(1080px * var(--slide-scale)); }
42
+ .slide[data-size="4x3"] { width: calc(1600px * var(--slide-scale)); height: calc(1200px * var(--slide-scale)); }
43
+ .slide[data-size="a4-portrait"] { width: calc(210mm * var(--slide-scale)); height: calc(297mm * var(--slide-scale)); }
44
+ .slide[data-size="a4-landscape"] { width: calc(297mm * var(--slide-scale)); height: calc(210mm * var(--slide-scale)); }
45
+ .slide[data-size="letter-portrait"] { width: calc(8.5in * var(--slide-scale)); height: calc(11in * var(--slide-scale)); }
46
+ .slide[data-size="letter-landscape"] { width: calc(11in * var(--slide-scale)); height: calc(8.5in * var(--slide-scale)); }
47
+ .slide[data-size="linkedin-banner"] { width: calc(1584px * var(--slide-scale)); height: calc(396px * var(--slide-scale)); }
47
48
  `.trim();
48
49
  var dynamicSizeStyles = `
49
50
  .slide[data-size="16x9"] { width: 100%; height: auto; aspect-ratio: 16 / 9; }
@@ -72,6 +73,40 @@ var printSizeStyles = `
72
73
  @page { size: Letter landscape; margin: 0; }
73
74
  }
74
75
  `.trim();
76
+ var hoverableSelector = [
77
+ '[data-node-type]:not([data-node-type="slide"])',
78
+ "p",
79
+ "h1",
80
+ "h2",
81
+ "h3",
82
+ "h4",
83
+ "h5",
84
+ "h6",
85
+ "blockquote",
86
+ "ul",
87
+ "ol",
88
+ "li",
89
+ "pre",
90
+ "figure",
91
+ "table"
92
+ ].join(", ");
93
+ var hoverOutlineStyles = `
94
+ .slide[data-hover-outline="on"]
95
+ :where(${hoverableSelector}):hover:not(:has(:hover)) {
96
+ outline: var(--slide-hover-outline-width, 1.5px) solid var(--slide-hover-outline-color, rgba(59, 130, 246, 0.65));
97
+ outline-offset: var(--slide-hover-outline-offset, 4px);
98
+ transition: outline-color 120ms ease, outline-width 120ms ease;
99
+ }
100
+
101
+ /* Cascade: when hovering a container, outline its descendant blocks too */
102
+ .slide[data-hover-outline="on"][data-hover-outline-cascade="on"]
103
+ :where(${hoverableSelector}):hover
104
+ :where(${hoverableSelector}) {
105
+ outline: var(--slide-hover-outline-width, 1.5px) solid var(--slide-hover-outline-color, rgba(59, 130, 246, 0.65));
106
+ outline-offset: var(--slide-hover-outline-offset, 4px);
107
+ transition: outline-color 120ms ease, outline-width 120ms ease;
108
+ }
109
+ `.trim();
75
110
  var SlidePluginKey = new PluginKey("slide");
76
111
  var Slide = Node.create({
77
112
  name: "slide",
@@ -85,8 +120,11 @@ var Slide = Node.create({
85
120
  injectCSS: true,
86
121
  renderMode: "fixed",
87
122
  defaultSize: "16x9",
123
+ scale: 1,
88
124
  injectPrintCSS: true,
89
- injectNonce: void 0
125
+ injectNonce: void 0,
126
+ hoverOutline: false,
127
+ hoverOutlineCascade: false
90
128
  };
91
129
  },
92
130
  addAttributes() {
@@ -138,6 +176,9 @@ var Slide = Node.create({
138
176
  ...rest
139
177
  } = merged;
140
178
  const styleParts = [];
179
+ const hoverEnabled = this.options.hoverOutline !== false;
180
+ const hoverConfig = this.options.hoverOutline === true ? {} : this.options.hoverOutline || {};
181
+ const hoverCascade = !!this.options.hoverOutlineCascade;
141
182
  if (backgroundColor) {
142
183
  styleParts.push(`--slide-bg-color: ${backgroundColor}`);
143
184
  }
@@ -151,6 +192,16 @@ var Slide = Node.create({
151
192
  if (backgroundOverlayOpacity != null) {
152
193
  styleParts.push(`--slide-bg-overlay-opacity: ${backgroundOverlayOpacity}`);
153
194
  }
195
+ if (hoverEnabled) {
196
+ const {
197
+ color = "rgba(59, 130, 246, 0.65)",
198
+ width = "1.5px",
199
+ offset = "4px"
200
+ } = hoverConfig;
201
+ styleParts.push(`--slide-hover-outline-color: ${color}`);
202
+ styleParts.push(`--slide-hover-outline-width: ${width}`);
203
+ styleParts.push(`--slide-hover-outline-offset: ${offset}`);
204
+ }
154
205
  const style = [rest.style, styleParts.join("; ")].filter(Boolean).join("; ");
155
206
  const className = [rest.class, rest.className, "slide"].filter(Boolean).join(" ");
156
207
  delete rest.className;
@@ -162,6 +213,8 @@ var Slide = Node.create({
162
213
  class: className || "slide",
163
214
  "data-node-type": "slide",
164
215
  "data-bg-mode": backgroundMode || "none",
216
+ "data-hover-outline": hoverEnabled ? "on" : void 0,
217
+ "data-hover-outline-cascade": hoverCascade ? "on" : void 0,
165
218
  style: style || void 0
166
219
  },
167
220
  0
@@ -173,9 +226,14 @@ var Slide = Node.create({
173
226
  key: SlidePluginKey,
174
227
  state: {
175
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
+ }
176
234
  if (this.options.injectCSS && typeof document !== "undefined") {
177
235
  createStyleTag(slideStyles, this.options.injectNonce, "slide");
178
- const sizingCss = this.options.renderMode === "dynamic" ? dynamicSizeStyles : fixedSizeStyles;
236
+ const sizingCss = this.options.renderMode === "dynamic" ? dynamicSizeStyles : getFixedSizeStyles(this.options.scale);
179
237
  if (sizingCss) {
180
238
  createStyleTag(sizingCss, this.options.injectNonce, "slide-sizes");
181
239
  }
@@ -185,6 +243,13 @@ var Slide = Node.create({
185
243
  createStyleTag(printCss, this.options.injectNonce, "slide-print");
186
244
  }
187
245
  }
246
+ if (this.options.hoverOutline !== false) {
247
+ createStyleTag(
248
+ hoverOutlineStyles,
249
+ this.options.injectNonce,
250
+ "slide-hover-outline"
251
+ );
252
+ }
188
253
  }
189
254
  return {};
190
255
  },
@@ -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[data-size=\"16x9\"] { width: 1920px; height: 1080px; }\n.slide[data-size=\"4x3\"] { width: 1600px; height: 1200px; }\n.slide[data-size=\"a4-portrait\"] { width: 210mm; height: 297mm; }\n.slide[data-size=\"a4-landscape\"] { width: 297mm; height: 210mm; }\n.slide[data-size=\"letter-portrait\"] { width: 8.5in; height: 11in; }\n.slide[data-size=\"letter-landscape\"] { width: 11in; height: 8.5in; }\n.slide[data-size=\"linkedin-banner\"] { width: 1584px; height: 396px; }\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\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\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 };\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 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 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 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 }\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 * @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.2.1",
3
+ "version": "0.3.1",
4
4
  "description": "Slide node extension for BlockSlides slide editor",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -11,11 +11,11 @@
11
11
  "slide"
12
12
  ],
13
13
  "peerDependencies": {
14
- "@blockslides/core": "^0.1.0",
14
+ "@blockslides/core": "^0.2.0",
15
15
  "@blockslides/pm": "^0.1.0"
16
16
  },
17
17
  "dependencies": {
18
- "@blockslides/core": "^0.1.0",
18
+ "@blockslides/core": "^0.2.0",
19
19
  "@blockslides/pm": "^0.1.0"
20
20
  },
21
21
  "devDependencies": {
package/src/slide.ts CHANGED
@@ -37,14 +37,15 @@ const slideStyles = `
37
37
  }
38
38
  `;
39
39
 
40
- const fixedSizeStyles = `
41
- .slide[data-size="16x9"] { width: 1920px; height: 1080px; }
42
- .slide[data-size="4x3"] { width: 1600px; height: 1200px; }
43
- .slide[data-size="a4-portrait"] { width: 210mm; height: 297mm; }
44
- .slide[data-size="a4-landscape"] { width: 297mm; height: 210mm; }
45
- .slide[data-size="letter-portrait"] { width: 8.5in; height: 11in; }
46
- .slide[data-size="letter-landscape"] { width: 11in; height: 8.5in; }
47
- .slide[data-size="linkedin-banner"] { width: 1584px; height: 396px; }
40
+ const getFixedSizeStyles = (scale: number) => `
41
+ .slide { --slide-scale: ${scale}; }
42
+ .slide[data-size="16x9"] { width: calc(1920px * var(--slide-scale)); height: calc(1080px * var(--slide-scale)); }
43
+ .slide[data-size="4x3"] { width: calc(1600px * var(--slide-scale)); height: calc(1200px * var(--slide-scale)); }
44
+ .slide[data-size="a4-portrait"] { width: calc(210mm * var(--slide-scale)); height: calc(297mm * var(--slide-scale)); }
45
+ .slide[data-size="a4-landscape"] { width: calc(297mm * var(--slide-scale)); height: calc(210mm * var(--slide-scale)); }
46
+ .slide[data-size="letter-portrait"] { width: calc(8.5in * var(--slide-scale)); height: calc(11in * var(--slide-scale)); }
47
+ .slide[data-size="letter-landscape"] { width: calc(11in * var(--slide-scale)); height: calc(8.5in * var(--slide-scale)); }
48
+ .slide[data-size="linkedin-banner"] { width: calc(1584px * var(--slide-scale)); height: calc(396px * var(--slide-scale)); }
48
49
  `.trim();
49
50
 
50
51
  const dynamicSizeStyles = `
@@ -76,6 +77,42 @@ const printSizeStyles = `
76
77
  }
77
78
  `.trim();
78
79
 
80
+ const hoverableSelector = [
81
+ '[data-node-type]:not([data-node-type="slide"])',
82
+ "p",
83
+ "h1",
84
+ "h2",
85
+ "h3",
86
+ "h4",
87
+ "h5",
88
+ "h6",
89
+ "blockquote",
90
+ "ul",
91
+ "ol",
92
+ "li",
93
+ "pre",
94
+ "figure",
95
+ "table",
96
+ ].join(", ");
97
+
98
+ const hoverOutlineStyles = `
99
+ .slide[data-hover-outline="on"]
100
+ :where(${hoverableSelector}):hover:not(:has(:hover)) {
101
+ outline: var(--slide-hover-outline-width, 1.5px) solid var(--slide-hover-outline-color, rgba(59, 130, 246, 0.65));
102
+ outline-offset: var(--slide-hover-outline-offset, 4px);
103
+ transition: outline-color 120ms ease, outline-width 120ms ease;
104
+ }
105
+
106
+ /* Cascade: when hovering a container, outline its descendant blocks too */
107
+ .slide[data-hover-outline="on"][data-hover-outline-cascade="on"]
108
+ :where(${hoverableSelector}):hover
109
+ :where(${hoverableSelector}) {
110
+ outline: var(--slide-hover-outline-width, 1.5px) solid var(--slide-hover-outline-color, rgba(59, 130, 246, 0.65));
111
+ outline-offset: var(--slide-hover-outline-offset, 4px);
112
+ transition: outline-color 120ms ease, outline-width 120ms ease;
113
+ }
114
+ `.trim();
115
+
79
116
  export interface SlideOptions {
80
117
  /**
81
118
  * The HTML attributes for a slide node.
@@ -100,6 +137,12 @@ export interface SlideOptions {
100
137
  * @default '16x9'
101
138
  */
102
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
+ * @default 1
144
+ */
145
+ scale: number;
103
146
  /**
104
147
  * Inject @media print/@page CSS for paper sizes
105
148
  * @default true
@@ -109,6 +152,25 @@ export interface SlideOptions {
109
152
  * Content Security Policy nonce
110
153
  */
111
154
  injectNonce?: string;
155
+ /**
156
+ * Hover outline highlight for nodes within a slide.
157
+ * - false: disabled (default)
158
+ * - true: enabled with defaults
159
+ * - object: enabled with overrides
160
+ */
161
+ hoverOutline:
162
+ | false
163
+ | true
164
+ | {
165
+ color?: string;
166
+ width?: string;
167
+ offset?: string;
168
+ };
169
+ /**
170
+ * When enabled, hovering a container outlines its descendant blocks too.
171
+ * @default false
172
+ */
173
+ hoverOutlineCascade?: boolean;
112
174
  }
113
175
 
114
176
  const SlidePluginKey = new PluginKey("slide");
@@ -128,8 +190,11 @@ export const Slide = Node.create<SlideOptions>({
128
190
  injectCSS: true,
129
191
  renderMode: "fixed",
130
192
  defaultSize: "16x9",
193
+ scale: 1,
131
194
  injectPrintCSS: true,
132
195
  injectNonce: undefined,
196
+ hoverOutline: false,
197
+ hoverOutlineCascade: false,
133
198
  };
134
199
  },
135
200
 
@@ -186,6 +251,11 @@ export const Slide = Node.create<SlideOptions>({
186
251
 
187
252
  const styleParts: string[] = [];
188
253
 
254
+ const hoverEnabled = this.options.hoverOutline !== false;
255
+ const hoverConfig =
256
+ this.options.hoverOutline === true ? {} : (this.options.hoverOutline || {});
257
+ const hoverCascade = !!this.options.hoverOutlineCascade;
258
+
189
259
  if (backgroundColor) {
190
260
  styleParts.push(`--slide-bg-color: ${backgroundColor}`);
191
261
  }
@@ -203,6 +273,17 @@ export const Slide = Node.create<SlideOptions>({
203
273
  styleParts.push(`--slide-bg-overlay-opacity: ${backgroundOverlayOpacity}`);
204
274
  }
205
275
 
276
+ if (hoverEnabled) {
277
+ const {
278
+ color = "rgba(59, 130, 246, 0.65)",
279
+ width = "1.5px",
280
+ offset = "4px",
281
+ } = hoverConfig;
282
+ styleParts.push(`--slide-hover-outline-color: ${color}`);
283
+ styleParts.push(`--slide-hover-outline-width: ${width}`);
284
+ styleParts.push(`--slide-hover-outline-offset: ${offset}`);
285
+ }
286
+
206
287
  const style = [rest.style, styleParts.join("; ")].filter(Boolean).join("; ");
207
288
 
208
289
  const className = [rest.class, rest.className, "slide"].filter(Boolean).join(" ");
@@ -217,6 +298,8 @@ export const Slide = Node.create<SlideOptions>({
217
298
  class: className || "slide",
218
299
  "data-node-type": "slide",
219
300
  "data-bg-mode": backgroundMode || "none",
301
+ "data-hover-outline": hoverEnabled ? "on" : undefined,
302
+ "data-hover-outline-cascade": hoverCascade ? "on" : undefined,
220
303
  style: style || undefined,
221
304
  },
222
305
  0,
@@ -229,10 +312,20 @@ export const Slide = Node.create<SlideOptions>({
229
312
  key: SlidePluginKey,
230
313
  state: {
231
314
  init: () => {
315
+ // Warn if scale is set but renderMode is not 'fixed'
316
+ if (this.options.scale !== 1 && this.options.renderMode !== "fixed") {
317
+ console.warn(
318
+ `[Slide] The 'scale' option only applies when renderMode is 'fixed'. ` +
319
+ `Current renderMode is '${this.options.renderMode}', so the scale value of ${this.options.scale} will be ignored.`
320
+ );
321
+ }
322
+
232
323
  if (this.options.injectCSS && typeof document !== "undefined") {
233
324
  createStyleTag(slideStyles, this.options.injectNonce, "slide");
234
325
  const sizingCss =
235
- this.options.renderMode === "dynamic" ? dynamicSizeStyles : fixedSizeStyles;
326
+ this.options.renderMode === "dynamic"
327
+ ? dynamicSizeStyles
328
+ : getFixedSizeStyles(this.options.scale);
236
329
  if (sizingCss) {
237
330
  createStyleTag(sizingCss, this.options.injectNonce, "slide-sizes");
238
331
  }
@@ -242,6 +335,13 @@ export const Slide = Node.create<SlideOptions>({
242
335
  createStyleTag(printCss, this.options.injectNonce, "slide-print");
243
336
  }
244
337
  }
338
+ if (this.options.hoverOutline !== false) {
339
+ createStyleTag(
340
+ hoverOutlineStyles,
341
+ this.options.injectNonce,
342
+ "slide-hover-outline"
343
+ );
344
+ }
245
345
  }
246
346
  return {};
247
347
  },