@textcortex/slidewise 1.8.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@textcortex/slidewise",
3
- "version": "1.8.0",
3
+ "version": "1.9.0",
4
4
  "description": "Embeddable React PPTX editor.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -65,12 +65,17 @@ function TextView({
65
65
  : el.vAlign === "middle"
66
66
  ? "center"
67
67
  : "flex-end",
68
+ background: el.background,
69
+ padding: el.padding
70
+ ? `${el.padding.t}px ${el.padding.r}px ${el.padding.b}px ${el.padding.l}px`
71
+ : undefined,
72
+ boxSizing: el.padding ? "border-box" : undefined,
68
73
  cursor: editing ? "text" : "inherit",
69
74
  };
70
75
  const inner: React.CSSProperties = {
71
76
  width: "100%",
72
77
  color: el.color,
73
- fontFamily: el.fontFamily,
78
+ fontFamily: withGenericFallback(el.fontFamily),
74
79
  fontSize: el.fontSize,
75
80
  fontWeight: el.fontWeight,
76
81
  fontStyle: el.italic ? "italic" : "normal",
@@ -85,11 +90,40 @@ function TextView({
85
90
  outline: "none",
86
91
  };
87
92
 
93
+ const backingPath = el.backingPath;
94
+ const positionedOuter: React.CSSProperties = backingPath
95
+ ? { ...outer, position: "relative" }
96
+ : outer;
97
+ const innerStacked: React.CSSProperties = backingPath
98
+ ? { ...inner, position: "relative", zIndex: 1 }
99
+ : inner;
100
+ const backingSvg = backingPath ? (
101
+ <svg
102
+ viewBox={`0 0 ${backingPath.viewW} ${backingPath.viewH}`}
103
+ preserveAspectRatio="none"
104
+ style={{
105
+ position: "absolute",
106
+ inset: 0,
107
+ width: "100%",
108
+ height: "100%",
109
+ pointerEvents: "none",
110
+ zIndex: 0,
111
+ }}
112
+ >
113
+ <path
114
+ d={backingPath.d}
115
+ fill={backingPath.fill}
116
+ fillRule={backingPath.fillRule ?? "nonzero"}
117
+ />
118
+ </svg>
119
+ ) : null;
120
+
88
121
  if (editing) {
89
122
  return (
90
- <div style={outer}>
123
+ <div style={positionedOuter}>
124
+ {backingSvg}
91
125
  <EditableText
92
- style={inner}
126
+ style={innerStacked}
93
127
  initialText={el.text}
94
128
  initialRuns={el.runs}
95
129
  onCommit={(t, r) => onCommit?.(t, r)}
@@ -100,8 +134,9 @@ function TextView({
100
134
 
101
135
  if (el.runs && el.runs.length) {
102
136
  return (
103
- <div style={outer}>
104
- <div style={inner}>
137
+ <div style={positionedOuter}>
138
+ {backingSvg}
139
+ <div style={innerStacked}>
105
140
  {el.runs.map((r, i) => (
106
141
  <span key={i} style={runCssStyle(r)}>
107
142
  {r.text}
@@ -113,15 +148,40 @@ function TextView({
113
148
  }
114
149
 
115
150
  return (
116
- <div style={outer}>
117
- <div style={inner}>{el.text}</div>
151
+ <div style={positionedOuter}>
152
+ {backingSvg}
153
+ <div style={innerStacked}>{el.text}</div>
118
154
  </div>
119
155
  );
120
156
  }
121
157
 
158
+ /**
159
+ * Append a `sans-serif` generic so brand families imported from PPTX
160
+ * (e.g. "EON Office Head") degrade gracefully when the typeface isn't
161
+ * installed locally — without the generic the browser silently picks
162
+ * its default serif. Already-qualified stacks (containing a comma) and
163
+ * plain generics ("serif"/"monospace") pass through untouched.
164
+ */
165
+ function withGenericFallback(family: string | undefined): string | undefined {
166
+ if (!family) return family;
167
+ if (family.includes(",")) return family;
168
+ const lower = family.trim().toLowerCase();
169
+ if (
170
+ lower === "serif" ||
171
+ lower === "sans-serif" ||
172
+ lower === "monospace" ||
173
+ lower === "cursive" ||
174
+ lower === "fantasy" ||
175
+ lower === "system-ui"
176
+ ) {
177
+ return family;
178
+ }
179
+ return `${family}, sans-serif`;
180
+ }
181
+
122
182
  function runCssStyle(r: TextRun): React.CSSProperties {
123
183
  const s: React.CSSProperties = {};
124
- if (r.fontFamily) s.fontFamily = r.fontFamily;
184
+ if (r.fontFamily) s.fontFamily = withGenericFallback(r.fontFamily);
125
185
  if (r.fontSize) s.fontSize = r.fontSize;
126
186
  if (r.fontWeight) s.fontWeight = r.fontWeight;
127
187
  if (r.color) s.color = r.color;
@@ -315,6 +375,27 @@ function sameStyle(a: TextRun, b: TextRun): boolean {
315
375
  function ShapeView({ el }: { el: ShapeElement }) {
316
376
  const stroke = el.stroke ?? "transparent";
317
377
  const sw = el.strokeWidth ?? 0;
378
+ // Custom vector path (PPTX <a:custGeom>) takes precedence over the preset
379
+ // kind — the path coordinates already encode the actual silhouette.
380
+ if (el.path) {
381
+ return (
382
+ <svg
383
+ viewBox={`0 0 ${el.path.viewW} ${el.path.viewH}`}
384
+ preserveAspectRatio="none"
385
+ width="100%"
386
+ height="100%"
387
+ >
388
+ <path
389
+ d={el.path.d}
390
+ fill={el.fill}
391
+ fillRule={el.path.fillRule ?? "nonzero"}
392
+ stroke={stroke}
393
+ strokeWidth={sw || undefined}
394
+ vectorEffect="non-scaling-stroke"
395
+ />
396
+ </svg>
397
+ );
398
+ }
318
399
  if (el.shape === "rect" || el.shape === "rounded") {
319
400
  return (
320
401
  <div
@@ -474,7 +555,9 @@ function LineView({ el }: { el: LineElement }) {
474
555
  function TableView({ el }: { el: TableElement }) {
475
556
  const cols = el.rows[0]?.length ?? 1;
476
557
  // PPTX-faithful: contiguous cells, no inter-cell gap, no rounded corners.
477
- // Earlier "card grid" styling drifted too far from the source look.
558
+ // Cells share their dividers via inset box-shadows so we draw a single
559
+ // grid line between adjacent cells instead of doubling-up borders.
560
+ const stroke = el.borderColor ?? "rgba(0, 0, 0, 0.12)";
478
561
  return (
479
562
  <div
480
563
  style={{
@@ -485,6 +568,7 @@ function TableView({ el }: { el: TableElement }) {
485
568
  height: "100%",
486
569
  gap: 0,
487
570
  background: "transparent",
571
+ boxShadow: `inset 0 0 0 1px ${stroke}`,
488
572
  }}
489
573
  >
490
574
  {el.rows.flatMap((row, ri) =>
@@ -504,6 +588,10 @@ function TableView({ el }: { el: TableElement }) {
504
588
  minHeight: 0,
505
589
  overflow: "hidden",
506
590
  wordBreak: "break-word",
591
+ borderRight:
592
+ ci < cols - 1 ? `1px solid ${stroke}` : undefined,
593
+ borderBottom:
594
+ ri < el.rows.length - 1 ? `1px solid ${stroke}` : undefined,
507
595
  }}
508
596
  >
509
597
  {cell}