@bendyline/squisq 1.1.1 → 1.2.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/{Doc-Cvt2lyzn.d.ts → Doc-Dwn5aHFC.d.ts} +60 -3
- package/dist/__tests__/dataTable.test.d.ts +2 -0
- package/dist/__tests__/dataTable.test.d.ts.map +1 -0
- package/dist/__tests__/dataTable.test.js +110 -0
- package/dist/__tests__/dataTable.test.js.map +1 -0
- package/dist/{chunk-CORGFIFU.js → chunk-KEFWM2VC.js} +1 -1
- package/dist/{chunk-CORGFIFU.js.map → chunk-KEFWM2VC.js.map} +1 -1
- package/dist/{chunk-OFU53HI7.js → chunk-QM5PKNPW.js} +1 -1
- package/dist/{chunk-OFU53HI7.js.map → chunk-QM5PKNPW.js.map} +1 -1
- package/dist/{chunk-3WBJAZZ5.js → chunk-T6WBC3ND.js} +67 -3
- package/dist/chunk-T6WBC3ND.js.map +1 -0
- package/dist/doc/templates/dataTable.d.ts +14 -0
- package/dist/doc/templates/dataTable.d.ts.map +1 -0
- package/dist/doc/templates/dataTable.js +73 -0
- package/dist/doc/templates/dataTable.js.map +1 -0
- package/dist/doc/templates/index.d.ts +1 -0
- package/dist/doc/templates/index.d.ts.map +1 -1
- package/dist/doc/templates/index.js +3 -0
- package/dist/doc/templates/index.js.map +1 -1
- package/dist/schemas/BlockTemplates.d.ts +18 -1
- package/dist/schemas/BlockTemplates.d.ts.map +1 -1
- package/dist/schemas/BlockTemplates.js.map +1 -1
- package/dist/schemas/Doc.d.ts +41 -1
- package/dist/schemas/Doc.d.ts.map +1 -1
- package/dist/schemas/Doc.js.map +1 -1
- package/dist/story/index.d.ts +17 -4
- package/dist/story/index.js +4 -2
- package/dist/{themeLibrary-VOHfPzRB.d.ts → themeLibrary-DLJtGh8-.d.ts} +1 -1
- package/package.json +13 -13
- package/src/__tests__/dataTable.test.ts +135 -0
- package/src/doc/templates/dataTable.ts +84 -0
- package/src/doc/templates/index.ts +3 -0
- package/src/schemas/BlockTemplates.ts +20 -1
- package/src/schemas/Doc.ts +43 -1
- package/dist/chunk-3WBJAZZ5.js.map +0 -1
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { getLayers, RenderContext } from '../doc/getLayers.js';
|
|
3
|
+
import { DEFAULT_THEME } from '../schemas/themeLibrary.js';
|
|
4
|
+
import { VIEWPORT_PRESETS } from '../schemas/Viewport.js';
|
|
5
|
+
import type { TemplateBlock } from '../schemas/BlockTemplates.js';
|
|
6
|
+
import type { TableLayer } from '../schemas/Doc.js';
|
|
7
|
+
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Helpers
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
function makeDataTableBlock(overrides: Record<string, unknown> = {}): TemplateBlock {
|
|
13
|
+
return {
|
|
14
|
+
template: 'dataTable',
|
|
15
|
+
id: 'dt-1',
|
|
16
|
+
duration: 5,
|
|
17
|
+
audioSegment: 0,
|
|
18
|
+
headers: ['Name', 'Value'],
|
|
19
|
+
rows: [
|
|
20
|
+
['Alpha', '100'],
|
|
21
|
+
['Beta', '200'],
|
|
22
|
+
],
|
|
23
|
+
...overrides,
|
|
24
|
+
} as TemplateBlock;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const defaultContext: RenderContext = {
|
|
28
|
+
theme: DEFAULT_THEME,
|
|
29
|
+
viewport: VIEWPORT_PRESETS.landscape,
|
|
30
|
+
blockIndex: 0,
|
|
31
|
+
totalBlocks: 5,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
// Tests
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
describe('dataTable template', () => {
|
|
39
|
+
it('produces layers including a table layer', () => {
|
|
40
|
+
const layers = getLayers(makeDataTableBlock(), defaultContext);
|
|
41
|
+
expect(layers.length).toBeGreaterThan(0);
|
|
42
|
+
|
|
43
|
+
const tableLayer = layers.find((l) => l.type === 'table');
|
|
44
|
+
expect(tableLayer).toBeDefined();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('table layer contains the correct headers', () => {
|
|
48
|
+
const layers = getLayers(makeDataTableBlock(), defaultContext);
|
|
49
|
+
const tableLayer = layers.find((l) => l.type === 'table') as TableLayer;
|
|
50
|
+
|
|
51
|
+
expect(tableLayer.content.headers).toEqual(['Name', 'Value']);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('table layer contains the correct rows', () => {
|
|
55
|
+
const layers = getLayers(makeDataTableBlock(), defaultContext);
|
|
56
|
+
const tableLayer = layers.find((l) => l.type === 'table') as TableLayer;
|
|
57
|
+
|
|
58
|
+
expect(tableLayer.content.rows).toEqual([
|
|
59
|
+
['Alpha', '100'],
|
|
60
|
+
['Beta', '200'],
|
|
61
|
+
]);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('includes a title text layer when title is provided', () => {
|
|
65
|
+
const layers = getLayers(makeDataTableBlock({ title: 'My Table' }), defaultContext);
|
|
66
|
+
const textLayers = layers.filter((l) => l.type === 'text');
|
|
67
|
+
|
|
68
|
+
expect(textLayers.length).toBeGreaterThanOrEqual(1);
|
|
69
|
+
const titleLayer = textLayers.find((l) => l.type === 'text' && l.content.text === 'My Table');
|
|
70
|
+
expect(titleLayer).toBeDefined();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('omits title layer when no title is given', () => {
|
|
74
|
+
const layers = getLayers(makeDataTableBlock({ title: undefined }), defaultContext);
|
|
75
|
+
const textLayers = layers.filter((l) => l.type === 'text');
|
|
76
|
+
|
|
77
|
+
// Should have no text layers (only bg + table)
|
|
78
|
+
expect(textLayers.length).toBe(0);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('includes a background layer', () => {
|
|
82
|
+
const layers = getLayers(makeDataTableBlock(), defaultContext);
|
|
83
|
+
// Background is the first layer (shape with gradient fill)
|
|
84
|
+
expect(['shape', 'image']).toContain(layers[0].type);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('passes column alignment to the table layer', () => {
|
|
88
|
+
const layers = getLayers(makeDataTableBlock({ align: ['left', 'right'] }), defaultContext);
|
|
89
|
+
const tableLayer = layers.find((l) => l.type === 'table') as TableLayer;
|
|
90
|
+
|
|
91
|
+
expect(tableLayer.content.align).toEqual(['left', 'right']);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('applies theme colors to table styling', () => {
|
|
95
|
+
const layers = getLayers(makeDataTableBlock(), defaultContext);
|
|
96
|
+
const tableLayer = layers.find((l) => l.type === 'table') as TableLayer;
|
|
97
|
+
const style = tableLayer.content.style;
|
|
98
|
+
|
|
99
|
+
// Style fields should be populated strings/numbers
|
|
100
|
+
expect(style.headerBackground).toBeTruthy();
|
|
101
|
+
expect(style.headerColor).toBeTruthy();
|
|
102
|
+
expect(style.cellColor).toBeTruthy();
|
|
103
|
+
expect(style.borderColor).toBeTruthy();
|
|
104
|
+
expect(typeof style.fontSize).toBe('number');
|
|
105
|
+
expect(style.fontSize).toBeGreaterThan(0);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('adjusts table position when title is present vs absent', () => {
|
|
109
|
+
const withTitle = getLayers(makeDataTableBlock({ title: 'Title' }), defaultContext);
|
|
110
|
+
const withoutTitle = getLayers(makeDataTableBlock({ title: undefined }), defaultContext);
|
|
111
|
+
|
|
112
|
+
const tableWithTitle = withTitle.find((l) => l.type === 'table') as TableLayer;
|
|
113
|
+
const tableWithoutTitle = withoutTitle.find((l) => l.type === 'table') as TableLayer;
|
|
114
|
+
|
|
115
|
+
// Table should be positioned lower when a title is present
|
|
116
|
+
expect(tableWithTitle.position.y).not.toBe(tableWithoutTitle.position.y);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('works with portrait viewport', () => {
|
|
120
|
+
const portraitContext: RenderContext = {
|
|
121
|
+
...defaultContext,
|
|
122
|
+
viewport: VIEWPORT_PRESETS.portrait,
|
|
123
|
+
};
|
|
124
|
+
const layers = getLayers(makeDataTableBlock(), portraitContext);
|
|
125
|
+
expect(layers.length).toBeGreaterThan(0);
|
|
126
|
+
const tableLayer = layers.find((l) => l.type === 'table');
|
|
127
|
+
expect(tableLayer).toBeDefined();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('handles empty rows gracefully', () => {
|
|
131
|
+
const layers = getLayers(makeDataTableBlock({ headers: ['A', 'B'], rows: [] }), defaultContext);
|
|
132
|
+
const tableLayer = layers.find((l) => l.type === 'table') as TableLayer;
|
|
133
|
+
expect(tableLayer.content.rows).toEqual([]);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data Table Template
|
|
3
|
+
*
|
|
4
|
+
* Renders a themed table with header row and data rows.
|
|
5
|
+
* Uses a TableLayer (foreignObject-based HTML table inside SVG)
|
|
6
|
+
* for proper table layout within the viewport.
|
|
7
|
+
*
|
|
8
|
+
* Adapts font sizes for different viewports and uses theme colors
|
|
9
|
+
* for header background, text, borders, and body cells.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { Layer } from '../../schemas/Doc.js';
|
|
13
|
+
import type { DataTableInput, TemplateContext } from '../../schemas/BlockTemplates.js';
|
|
14
|
+
import { scaledFontSize } from '../../schemas/BlockTemplates.js';
|
|
15
|
+
import { getThemeFont, resolveColorScheme } from '../utils/themeUtils.js';
|
|
16
|
+
import { createBackgroundLayer } from './captionUtils.js';
|
|
17
|
+
|
|
18
|
+
export function dataTable(input: DataTableInput, context: TemplateContext): Layer[] {
|
|
19
|
+
const { title, headers, rows, align, colorScheme } = input;
|
|
20
|
+
const { theme } = context;
|
|
21
|
+
|
|
22
|
+
const colors = resolveColorScheme(context, colorScheme);
|
|
23
|
+
const titleFontSize = scaledFontSize(48, context, true);
|
|
24
|
+
const tableFontSize = scaledFontSize(28, context, false);
|
|
25
|
+
|
|
26
|
+
const layers: Layer[] = [
|
|
27
|
+
createBackgroundLayer(
|
|
28
|
+
'bg',
|
|
29
|
+
`linear-gradient(170deg, ${theme.colors.background} 0%, #0f1520 100%)`,
|
|
30
|
+
),
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
// Optional title above the table
|
|
34
|
+
if (title) {
|
|
35
|
+
layers.push({
|
|
36
|
+
type: 'text',
|
|
37
|
+
id: 'title',
|
|
38
|
+
content: {
|
|
39
|
+
text: title,
|
|
40
|
+
style: {
|
|
41
|
+
fontSize: titleFontSize,
|
|
42
|
+
fontFamily: getThemeFont(context, 'title'),
|
|
43
|
+
fontWeight: 'bold',
|
|
44
|
+
color: theme.colors.text,
|
|
45
|
+
textAlign: 'center',
|
|
46
|
+
shadow: true,
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
position: { x: '50%', y: '10%', width: '80%', anchor: 'center' },
|
|
50
|
+
animation: { type: 'fadeIn', duration: 0.8 },
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Table layer
|
|
55
|
+
layers.push({
|
|
56
|
+
type: 'table',
|
|
57
|
+
id: 'table',
|
|
58
|
+
content: {
|
|
59
|
+
headers,
|
|
60
|
+
rows,
|
|
61
|
+
align,
|
|
62
|
+
style: {
|
|
63
|
+
headerBackground: colors.accent,
|
|
64
|
+
headerColor: colors.text,
|
|
65
|
+
cellBackground: 'rgba(255,255,255,0.05)',
|
|
66
|
+
cellColor: theme.colors.text,
|
|
67
|
+
borderColor: 'rgba(255,255,255,0.12)',
|
|
68
|
+
fontSize: tableFontSize,
|
|
69
|
+
fontFamily: getThemeFont(context, 'body'),
|
|
70
|
+
headerFontFamily: getThemeFont(context, 'title'),
|
|
71
|
+
borderRadius: 8,
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
position: {
|
|
75
|
+
x: '10%',
|
|
76
|
+
y: title ? '18%' : '8%',
|
|
77
|
+
width: '80%',
|
|
78
|
+
height: title ? '74%' : '84%',
|
|
79
|
+
},
|
|
80
|
+
animation: { type: 'fadeIn', duration: 1, delay: title ? 0.4 : 0 },
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
return layers;
|
|
84
|
+
}
|
|
@@ -44,6 +44,7 @@ import { comparisonBar } from './comparisonBar.js';
|
|
|
44
44
|
import { pullQuote } from './pullQuote.js';
|
|
45
45
|
import { videoWithCaption } from './videoWithCaption.js';
|
|
46
46
|
import { videoPullQuote } from './videoPullQuote.js';
|
|
47
|
+
import { dataTable } from './dataTable.js';
|
|
47
48
|
|
|
48
49
|
/**
|
|
49
50
|
* Registry mapping template names to their implementation functions.
|
|
@@ -68,6 +69,7 @@ export const templateRegistry: TemplateRegistry = {
|
|
|
68
69
|
pullQuote,
|
|
69
70
|
videoWithCaption,
|
|
70
71
|
videoPullQuote,
|
|
72
|
+
dataTable,
|
|
71
73
|
};
|
|
72
74
|
|
|
73
75
|
/**
|
|
@@ -565,6 +567,7 @@ export { comparisonBar } from './comparisonBar.js';
|
|
|
565
567
|
export { pullQuote } from './pullQuote.js';
|
|
566
568
|
export { videoWithCaption } from './videoWithCaption.js';
|
|
567
569
|
export { videoPullQuote } from './videoPullQuote.js';
|
|
570
|
+
export { dataTable } from './dataTable.js';
|
|
568
571
|
|
|
569
572
|
// Re-export accent image utilities
|
|
570
573
|
export { getAccentLayout, createAccentLayers, adjustY, DEFAULT_LAYOUT } from './accentImage.js';
|
|
@@ -429,6 +429,24 @@ export interface VideoPullQuoteInput extends BaseTemplateBlock {
|
|
|
429
429
|
};
|
|
430
430
|
}
|
|
431
431
|
|
|
432
|
+
/**
|
|
433
|
+
* Data table - renders a themed table with headers and rows.
|
|
434
|
+
* Ideal for structured data, comparisons, and reference information.
|
|
435
|
+
*/
|
|
436
|
+
export interface DataTableInput extends BaseTemplateBlock {
|
|
437
|
+
template: 'dataTable';
|
|
438
|
+
/** Optional title displayed above the table */
|
|
439
|
+
title?: string;
|
|
440
|
+
/** Header cell values */
|
|
441
|
+
headers: string[];
|
|
442
|
+
/** Data rows (array of cell value arrays) */
|
|
443
|
+
rows: string[][];
|
|
444
|
+
/** Per-column alignment */
|
|
445
|
+
align?: (('left' | 'right' | 'center') | null)[];
|
|
446
|
+
/** Color scheme for the table header */
|
|
447
|
+
colorScheme?: ColorScheme;
|
|
448
|
+
}
|
|
449
|
+
|
|
432
450
|
/**
|
|
433
451
|
* Union of all template block types.
|
|
434
452
|
*/
|
|
@@ -449,7 +467,8 @@ export type TemplateBlock =
|
|
|
449
467
|
| ComparisonBarInput
|
|
450
468
|
| PullQuoteInput
|
|
451
469
|
| VideoWithCaptionInput
|
|
452
|
-
| VideoPullQuoteInput
|
|
470
|
+
| VideoPullQuoteInput
|
|
471
|
+
| DataTableInput;
|
|
453
472
|
|
|
454
473
|
/**
|
|
455
474
|
* A block can be either a raw Block or a TemplateBlock.
|
package/src/schemas/Doc.ts
CHANGED
|
@@ -177,7 +177,7 @@ export interface Block {
|
|
|
177
177
|
* A visual element within a block.
|
|
178
178
|
* Layers are composited back-to-front (first layer is background).
|
|
179
179
|
*/
|
|
180
|
-
export type Layer = ImageLayer | TextLayer | ShapeLayer | MapLayer | VideoLayer;
|
|
180
|
+
export type Layer = ImageLayer | TextLayer | ShapeLayer | MapLayer | VideoLayer | TableLayer;
|
|
181
181
|
|
|
182
182
|
interface BaseLayer {
|
|
183
183
|
/** Unique identifier for this layer */
|
|
@@ -300,6 +300,48 @@ export interface VideoLayer extends BaseLayer {
|
|
|
300
300
|
};
|
|
301
301
|
}
|
|
302
302
|
|
|
303
|
+
/**
|
|
304
|
+
* Table layer - renders a data table with themed styling.
|
|
305
|
+
* Uses foreignObject inside SVG for HTML-based table layout.
|
|
306
|
+
*/
|
|
307
|
+
export interface TableLayer extends BaseLayer {
|
|
308
|
+
type: 'table';
|
|
309
|
+
content: {
|
|
310
|
+
/** Header cell values */
|
|
311
|
+
headers: string[];
|
|
312
|
+
/** Data rows (array of cell value arrays) */
|
|
313
|
+
rows: string[][];
|
|
314
|
+
/** Per-column alignment */
|
|
315
|
+
align?: (('left' | 'right' | 'center') | null)[];
|
|
316
|
+
/** Visual styling */
|
|
317
|
+
style: TableLayerStyle;
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Styling options for a TableLayer.
|
|
323
|
+
*/
|
|
324
|
+
export interface TableLayerStyle {
|
|
325
|
+
/** Header row background color */
|
|
326
|
+
headerBackground: string;
|
|
327
|
+
/** Header row text color */
|
|
328
|
+
headerColor: string;
|
|
329
|
+
/** Body cell background color */
|
|
330
|
+
cellBackground: string;
|
|
331
|
+
/** Body cell text color */
|
|
332
|
+
cellColor: string;
|
|
333
|
+
/** Border/divider color */
|
|
334
|
+
borderColor: string;
|
|
335
|
+
/** Font size in pixels */
|
|
336
|
+
fontSize: number;
|
|
337
|
+
/** Font family */
|
|
338
|
+
fontFamily?: string;
|
|
339
|
+
/** Header font family (falls back to fontFamily) */
|
|
340
|
+
headerFontFamily?: string;
|
|
341
|
+
/** Corner radius for the table container */
|
|
342
|
+
borderRadius?: number;
|
|
343
|
+
}
|
|
344
|
+
|
|
303
345
|
/**
|
|
304
346
|
* Available map tile styles from free/open-source providers.
|
|
305
347
|
*/
|