@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 +22 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +75 -10
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +75 -10
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/slide.ts +109 -9
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
|
|
66
|
-
.slide
|
|
67
|
-
.slide[data-size="
|
|
68
|
-
.slide[data-size="
|
|
69
|
-
.slide[data-size="a4-
|
|
70
|
-
.slide[data-size="
|
|
71
|
-
.slide[data-size="letter-
|
|
72
|
-
.slide[data-size="
|
|
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 :
|
|
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":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,kBAAsD;AACtD,mBAAkC;AAElC,IAAM,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAoCpB,IAAM,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQtB,KAAK;AAEP,IAAM,oBAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQxB,KAAK;AAEP,IAAM,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBtB,KAAK;AAqCP,IAAM,iBAAiB,IAAI,uBAAU,OAAO;AAErC,IAAM,QAAQ,iBAAK,OAAqB;AAAA,EAC7C,MAAM;AAAA,EACN,WAAW;AAAA,EACX,SAAS;AAAA,EAET,OAAO;AAAA,EAEP,UAAU;AAAA,EAEV,aAAa;AACX,WAAO;AAAA,MACL,gBAAgB,CAAC;AAAA,MACjB,WAAW;AAAA,MACX,YAAY;AAAA,MACZ,aAAa;AAAA,MACb,gBAAgB;AAAA,MAChB,aAAa;AAAA,IACf;AAAA,EACF;AAAA,EAEA,gBAAgB;AACd,WAAO;AAAA,MACL,MAAM;AAAA,QACJ,SAAS,KAAK,QAAQ;AAAA,QACtB,WAAW,CAAC,YAAY,QAAQ,aAAa,WAAW,KAAK,KAAK,QAAQ;AAAA,QAC1E,YAAY,CAAC,eAAe;AAC1B,cAAI,CAAC,WAAW,MAAM;AACpB,mBAAO,EAAE,aAAa,KAAK,QAAQ,YAAY;AAAA,UACjD;AACA,iBAAO,EAAE,aAAa,WAAW,KAAK;AAAA,QACxC;AAAA,MACF;AAAA,MACA,WAAW;AAAA,QACT,SAAS;AAAA,MACX;AAAA,MACA,IAAI;AAAA,QACF,SAAS;AAAA,MACX;AAAA,MACA,gBAAgB;AAAA,QACd,SAAS;AAAA,MACX;AAAA,MACA,iBAAiB;AAAA,QACf,SAAS;AAAA,MACX;AAAA,MACA,iBAAiB;AAAA,QACf,SAAS;AAAA,MACX;AAAA,MACA,wBAAwB;AAAA,QACtB,SAAS;AAAA,MACX;AAAA,MACA,0BAA0B;AAAA,QACxB,SAAS;AAAA,MACX;AAAA,IACF;AAAA,EACF;AAAA,EAEA,YAAY;AACV,WAAO,CAAC,EAAE,KAAK,YAAY,CAAC;AAAA,EAC9B;AAAA,EAEA,WAAW,EAAE,eAAe,GAAG;AAC7B,UAAM,aAAS,6BAAgB,KAAK,QAAQ,gBAAgB,cAAc;AAC1E,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,GAAG;AAAA,IACL,IAAI;AAEJ,UAAM,aAAuB,CAAC;AAE9B,QAAI,iBAAiB;AACnB,iBAAW,KAAK,qBAAqB,eAAe,EAAE;AAAA,IACxD;AAEA,QAAI,iBAAiB;AACnB,YAAM,UAAU,OAAO,eAAe,EAAE,QAAQ,MAAM,KAAK;AAC3D,iBAAW,KAAK,0BAA0B,OAAO,IAAI;AAAA,IACvD;AAEA,QAAI,wBAAwB;AAC1B,iBAAW,KAAK,6BAA6B,sBAAsB,EAAE;AAAA,IACvE;AAEA,QAAI,4BAA4B,MAAM;AACpC,iBAAW,KAAK,+BAA+B,wBAAwB,EAAE;AAAA,IAC3E;AAEA,UAAM,QAAQ,CAAC,KAAK,OAAO,WAAW,KAAK,IAAI,CAAC,EAAE,OAAO,OAAO,EAAE,KAAK,IAAI;AAE3E,UAAM,YAAY,CAAC,KAAK,OAAO,KAAK,WAAW,OAAO,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;AAEhF,WAAQ,KAAa;AACrB,WAAQ,KAAa;AAErB,WAAO;AAAA,MACL;AAAA,MACA;AAAA,QACE,GAAG;AAAA,QACH,OAAO,aAAa;AAAA,QACpB,kBAAkB;AAAA,QAClB,gBAAgB,kBAAkB;AAAA,QAClC,OAAO,SAAS;AAAA,MAClB;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EAEA,wBAAwB;AACtB,WAAO;AAAA,MACL,IAAI,oBAAO;AAAA,QACT,KAAK;AAAA,QACL,OAAO;AAAA,UACL,MAAM,MAAM;AACV,gBAAI,KAAK,QAAQ,aAAa,OAAO,aAAa,aAAa;AAC7D,8CAAe,aAAa,KAAK,QAAQ,aAAa,OAAO;AAC7D,oBAAM,YACJ,KAAK,QAAQ,eAAe,YAAY,oBAAoB;AAC9D,kBAAI,WAAW;AACb,gDAAe,WAAW,KAAK,QAAQ,aAAa,aAAa;AAAA,cACnE;AACA,kBAAI,KAAK,QAAQ,gBAAgB;AAC/B,sBAAM,WAAW;AACjB,oBAAI,UAAU;AACZ,kDAAe,UAAU,KAAK,QAAQ,aAAa,aAAa;AAAA,gBAClE;AAAA,cACF;AAAA,YACF;AACA,mBAAO,CAAC;AAAA,UACV;AAAA,UACA,OAAO,CAAC,KAAK,gBAAuC;AAAA,QACtD;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AACF,CAAC;","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
|
|
40
|
-
.slide
|
|
41
|
-
.slide[data-size="
|
|
42
|
-
.slide[data-size="
|
|
43
|
-
.slide[data-size="a4-
|
|
44
|
-
.slide[data-size="
|
|
45
|
-
.slide[data-size="letter-
|
|
46
|
-
.slide[data-size="
|
|
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 :
|
|
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
|
},
|
package/dist/index.mjs.map
CHANGED
|
@@ -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.
|
|
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.
|
|
14
|
+
"@blockslides/core": "^0.2.0",
|
|
15
15
|
"@blockslides/pm": "^0.1.0"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@blockslides/core": "^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
|
|
41
|
-
.slide
|
|
42
|
-
.slide[data-size="
|
|
43
|
-
.slide[data-size="
|
|
44
|
-
.slide[data-size="a4-
|
|
45
|
-
.slide[data-size="
|
|
46
|
-
.slide[data-size="letter-
|
|
47
|
-
.slide[data-size="
|
|
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"
|
|
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
|
},
|