@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.
Files changed (35) hide show
  1. package/dist/{Doc-Cvt2lyzn.d.ts → Doc-Dwn5aHFC.d.ts} +60 -3
  2. package/dist/__tests__/dataTable.test.d.ts +2 -0
  3. package/dist/__tests__/dataTable.test.d.ts.map +1 -0
  4. package/dist/__tests__/dataTable.test.js +110 -0
  5. package/dist/__tests__/dataTable.test.js.map +1 -0
  6. package/dist/{chunk-CORGFIFU.js → chunk-KEFWM2VC.js} +1 -1
  7. package/dist/{chunk-CORGFIFU.js.map → chunk-KEFWM2VC.js.map} +1 -1
  8. package/dist/{chunk-OFU53HI7.js → chunk-QM5PKNPW.js} +1 -1
  9. package/dist/{chunk-OFU53HI7.js.map → chunk-QM5PKNPW.js.map} +1 -1
  10. package/dist/{chunk-3WBJAZZ5.js → chunk-T6WBC3ND.js} +67 -3
  11. package/dist/chunk-T6WBC3ND.js.map +1 -0
  12. package/dist/doc/templates/dataTable.d.ts +14 -0
  13. package/dist/doc/templates/dataTable.d.ts.map +1 -0
  14. package/dist/doc/templates/dataTable.js +73 -0
  15. package/dist/doc/templates/dataTable.js.map +1 -0
  16. package/dist/doc/templates/index.d.ts +1 -0
  17. package/dist/doc/templates/index.d.ts.map +1 -1
  18. package/dist/doc/templates/index.js +3 -0
  19. package/dist/doc/templates/index.js.map +1 -1
  20. package/dist/schemas/BlockTemplates.d.ts +18 -1
  21. package/dist/schemas/BlockTemplates.d.ts.map +1 -1
  22. package/dist/schemas/BlockTemplates.js.map +1 -1
  23. package/dist/schemas/Doc.d.ts +41 -1
  24. package/dist/schemas/Doc.d.ts.map +1 -1
  25. package/dist/schemas/Doc.js.map +1 -1
  26. package/dist/story/index.d.ts +17 -4
  27. package/dist/story/index.js +4 -2
  28. package/dist/{themeLibrary-VOHfPzRB.d.ts → themeLibrary-DLJtGh8-.d.ts} +1 -1
  29. package/package.json +13 -13
  30. package/src/__tests__/dataTable.test.ts +135 -0
  31. package/src/doc/templates/dataTable.ts +84 -0
  32. package/src/doc/templates/index.ts +3 -0
  33. package/src/schemas/BlockTemplates.ts +20 -1
  34. package/src/schemas/Doc.ts +43 -1
  35. 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.
@@ -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
  */