aac-board-viewer 0.2.2 → 0.2.3

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/README.md CHANGED
@@ -14,6 +14,8 @@ Universal AAC (Augmentative and Alternative Communication) board viewer componen
14
14
  - **Excel** (`.xlsx` boards)
15
15
  - **DOT files** (`.dot` visualizations)
16
16
 
17
+ Note: In-browser loading of SQLite-backed formats (`.sps`, `.spb`, `.ce`) requires SQL.js configuration.
18
+
17
19
  ## Features
18
20
 
19
21
  - 🎯 **Universal Support** - Works with all major AAC file formats
@@ -40,9 +42,36 @@ yarn add aac-board-viewer
40
42
 
41
43
  ## Quick Start
42
44
 
43
- ### Server-Side / API Usage
45
+ ### Browser Usage
46
+
47
+ SQLite-backed formats (Snap `.sps`/`.spb` and TouchChat `.ce`) require SQL.js to be configured in your bundler before loading files:
48
+
49
+ ```tsx
50
+ import { configureBrowserSqlJs, loadAACFile, BoardViewer } from 'aac-board-viewer';
51
+ import sqlWasmUrl from 'sql.js/dist/sql-wasm.wasm?url';
52
+ import 'aac-board-viewer/styles';
53
+
54
+ configureBrowserSqlJs({
55
+ locateFile: () => sqlWasmUrl,
56
+ });
57
+
58
+ function MyViewer({ file }: { file: File }) {
59
+ const [tree, setTree] = useState(null);
60
+
61
+ useEffect(() => {
62
+ async function load() {
63
+ const loadedTree = await loadAACFile(file);
64
+ setTree(loadedTree);
65
+ }
66
+ load();
67
+ }, [file]);
44
68
 
45
- > Important: `@willwade/aac-processors` is a Node-only dependency (uses native modules like `better-sqlite3`). File parsing must happen server-side. For client apps, call an API that returns the parsed tree and pass that to `BoardViewer`.
69
+ if (!tree) return <div>Loading...</div>;
70
+ return <BoardViewer tree={tree} />;
71
+ }
72
+ ```
73
+
74
+ ### Server-Side / API Usage
46
75
 
47
76
  ```tsx
48
77
  import { BoardViewer } from 'aac-board-viewer';
@@ -60,6 +89,44 @@ function MyViewer({ treeData }) {
60
89
  }
61
90
  ```
62
91
 
92
+ ### Client/Server Split (Lightweight Frontend)
93
+
94
+ Parse boards on the server and send the `AACTree` JSON to the browser for rendering. This keeps the frontend lightweight while the backend handles heavy formats.
95
+
96
+ Server (Node/Express):
97
+
98
+ ```ts
99
+ import express from 'express';
100
+ import { loadAACFile } from 'aac-board-viewer';
101
+
102
+ const app = express();
103
+
104
+ app.get('/api/boards/:id', async (req, res) => {
105
+ const tree = await loadAACFile(`/data/boards/${req.params.id}.sps`);
106
+ res.json(tree);
107
+ });
108
+ ```
109
+
110
+ Client (React):
111
+
112
+ ```tsx
113
+ import { BoardViewer } from 'aac-board-viewer';
114
+ import 'aac-board-viewer/styles';
115
+
116
+ function RemoteBoard({ id }: { id: string }) {
117
+ const [tree, setTree] = useState(null);
118
+
119
+ useEffect(() => {
120
+ fetch(`/api/boards/${id}`)
121
+ .then((res) => res.json())
122
+ .then(setTree);
123
+ }, [id]);
124
+
125
+ if (!tree) return <div>Loading...</div>;
126
+ return <BoardViewer tree={tree} />;
127
+ }
128
+ ```
129
+
63
130
  ### With Metrics
64
131
 
65
132
  ```tsx
package/dist/index.d.mts CHANGED
@@ -271,11 +271,19 @@ declare function useSentenceBuilder(): {
271
271
  * Provides utilities for loading AAC files from various sources
272
272
  * (File/Blob objects, file paths, URLs) in both browser and server contexts.
273
273
  *
274
- * Browser: Supports .obf, .obz, .gridset, .plist, .grd, .opml, .dot
275
- * Node.js: Supports all formats including .sps, .spb, .ce (Node-only processors)
274
+ * Browser: Supports all formats. SQLite-backed formats (.sps, .spb, .ce) require SQL.js configuration.
275
+ * Node.js: Supports all formats including filesystem access and SQLite-backed formats.
276
276
  */
277
277
 
278
278
  type ProcessorOptions = Record<string, unknown> | undefined;
279
+ type SqlJsConfig = {
280
+ locateFile: (file: string) => string;
281
+ [key: string]: unknown;
282
+ };
283
+ /**
284
+ * Configure SQL.js for browser-only SQLite-backed formats (.sps/.spb/.ce).
285
+ */
286
+ declare function configureBrowserSqlJs(config: SqlJsConfig): Promise<void>;
279
287
  /**
280
288
  * Load an AAC file from a file path (Node.js only)
281
289
  *
@@ -403,4 +411,4 @@ declare function getBrowserExtensions(): string[];
403
411
  */
404
412
  declare function getNodeOnlyExtensions(): string[];
405
413
 
406
- export { BoardViewer, type BoardViewerProps, type ButtonMetric, type LoadAACFileResult, type MetricsOptions, calculateMetrics, getBrowserExtensions, getNodeOnlyExtensions, getSupportedFormats, isBrowserCompatible, loadAACFile, loadAACFileFromURL, loadAACFileWithMetadata, useAACFile, useAACFileFromFile, useAACFileFromFileWithMetrics, useAACFileWithMetrics, useMetrics, useSentenceBuilder };
414
+ export { BoardViewer, type BoardViewerProps, type ButtonMetric, type LoadAACFileResult, type MetricsOptions, calculateMetrics, configureBrowserSqlJs, getBrowserExtensions, getNodeOnlyExtensions, getSupportedFormats, isBrowserCompatible, loadAACFile, loadAACFileFromURL, loadAACFileWithMetadata, useAACFile, useAACFileFromFile, useAACFileFromFileWithMetrics, useAACFileWithMetrics, useMetrics, useSentenceBuilder };
package/dist/index.d.ts CHANGED
@@ -271,11 +271,19 @@ declare function useSentenceBuilder(): {
271
271
  * Provides utilities for loading AAC files from various sources
272
272
  * (File/Blob objects, file paths, URLs) in both browser and server contexts.
273
273
  *
274
- * Browser: Supports .obf, .obz, .gridset, .plist, .grd, .opml, .dot
275
- * Node.js: Supports all formats including .sps, .spb, .ce (Node-only processors)
274
+ * Browser: Supports all formats. SQLite-backed formats (.sps, .spb, .ce) require SQL.js configuration.
275
+ * Node.js: Supports all formats including filesystem access and SQLite-backed formats.
276
276
  */
277
277
 
278
278
  type ProcessorOptions = Record<string, unknown> | undefined;
279
+ type SqlJsConfig = {
280
+ locateFile: (file: string) => string;
281
+ [key: string]: unknown;
282
+ };
283
+ /**
284
+ * Configure SQL.js for browser-only SQLite-backed formats (.sps/.spb/.ce).
285
+ */
286
+ declare function configureBrowserSqlJs(config: SqlJsConfig): Promise<void>;
279
287
  /**
280
288
  * Load an AAC file from a file path (Node.js only)
281
289
  *
@@ -403,4 +411,4 @@ declare function getBrowserExtensions(): string[];
403
411
  */
404
412
  declare function getNodeOnlyExtensions(): string[];
405
413
 
406
- export { BoardViewer, type BoardViewerProps, type ButtonMetric, type LoadAACFileResult, type MetricsOptions, calculateMetrics, getBrowserExtensions, getNodeOnlyExtensions, getSupportedFormats, isBrowserCompatible, loadAACFile, loadAACFileFromURL, loadAACFileWithMetadata, useAACFile, useAACFileFromFile, useAACFileFromFileWithMetrics, useAACFileWithMetrics, useMetrics, useSentenceBuilder };
414
+ export { BoardViewer, type BoardViewerProps, type ButtonMetric, type LoadAACFileResult, type MetricsOptions, calculateMetrics, configureBrowserSqlJs, getBrowserExtensions, getNodeOnlyExtensions, getSupportedFormats, isBrowserCompatible, loadAACFile, loadAACFileFromURL, loadAACFileWithMetadata, useAACFile, useAACFileFromFile, useAACFileFromFileWithMetrics, useAACFileWithMetrics, useMetrics, useSentenceBuilder };
package/dist/index.js CHANGED
@@ -32,6 +32,7 @@ var index_exports = {};
32
32
  __export(index_exports, {
33
33
  BoardViewer: () => BoardViewer,
34
34
  calculateMetrics: () => calculateMetrics,
35
+ configureBrowserSqlJs: () => configureBrowserSqlJs,
35
36
  getBrowserExtensions: () => getBrowserExtensions,
36
37
  getNodeOnlyExtensions: () => getNodeOnlyExtensions,
37
38
  getSupportedFormats: () => getSupportedFormats,
@@ -288,16 +289,16 @@ function BoardViewer({
288
289
  }
289
290
  };
290
291
  const getTextColor = (backgroundColor) => {
291
- if (!backgroundColor) return "text-gray-900 dark:text-gray-100";
292
+ if (!backgroundColor) return "#111827";
292
293
  const hex = backgroundColor.replace("#", "");
293
294
  if (hex.length === 6) {
294
295
  const r = parseInt(hex.substring(0, 2), 16);
295
296
  const g = parseInt(hex.substring(2, 4), 16);
296
297
  const b = parseInt(hex.substring(4, 6), 16);
297
298
  const brightness = (r * 299 + g * 587 + b * 114) / 1e3;
298
- return brightness >= 128 ? "text-gray-900" : "text-white";
299
+ return brightness >= 128 ? "#111827" : "#f9fafb";
299
300
  }
300
- return "text-gray-900 dark:text-gray-100";
301
+ return "#111827";
301
302
  };
302
303
  if (!currentPage) {
303
304
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: `flex items-center justify-center h-96 bg-gray-100 dark:bg-gray-800 rounded-lg ${className}`, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "text-center", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-gray-600 dark:text-gray-400", children: "No pages available" }) }) });
@@ -370,7 +371,8 @@ function BoardViewer({
370
371
  className: "aspect-square p-1 rounded border border-gray-200 dark:border-gray-700 transition flex flex-col items-center justify-center gap-0.5 hover:bg-gray-200 dark:hover:bg-gray-700 relative",
371
372
  style: {
372
373
  backgroundColor: button.style?.backgroundColor || "#f3f4f6",
373
- borderColor: button.style?.borderColor || "#e5e7eb"
374
+ borderColor: button.style?.borderColor || "#e5e7eb",
375
+ color: button.style?.fontColor || getTextColor(button.style?.backgroundColor)
374
376
  },
375
377
  title: `${button.label}
376
378
  ${button.message || ""}`,
@@ -379,9 +381,7 @@ ${button.message || ""}`,
379
381
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
380
382
  "span",
381
383
  {
382
- className: `text-[8px] sm:text-[9px] text-center font-medium leading-tight line-clamp-2 ${getTextColor(
383
- button.style?.backgroundColor
384
- )}`,
384
+ className: "text-[8px] sm:text-[9px] text-center font-medium leading-tight line-clamp-2",
385
385
  children: button.label
386
386
  }
387
387
  )
@@ -495,7 +495,7 @@ ${button.message || ""}`,
495
495
  style: {
496
496
  backgroundColor: button.style?.backgroundColor || "#f3f4f6",
497
497
  borderColor: button.style?.borderColor || "#e5e7eb",
498
- color: button.style?.fontColor || void 0,
498
+ color: button.style?.fontColor || getTextColor(button.style?.backgroundColor),
499
499
  gridColumn: `${colIndex + 1} / span ${colSpan}`,
500
500
  gridRow: `${rowIndex + 1} / span ${rowSpan}`
501
501
  },
@@ -523,9 +523,7 @@ ${button.message || ""}`,
523
523
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
524
524
  "span",
525
525
  {
526
- className: `text-xs sm:text-sm text-center font-medium leading-tight line-clamp-3 ${getTextColor(
527
- button.style?.backgroundColor
528
- )}`,
526
+ className: "text-xs sm:text-sm text-center font-medium leading-tight line-clamp-3",
529
527
  children: button.label
530
528
  }
531
529
  ),
@@ -537,9 +535,7 @@ ${button.message || ""}`,
537
535
  button.message && button.message !== button.label && !isPredictionCell && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
538
536
  "span",
539
537
  {
540
- className: `text-[10px] sm:text-xs text-center opacity-75 line-clamp-2 ${getTextColor(
541
- button.style?.backgroundColor
542
- )}`,
538
+ className: "text-[10px] sm:text-xs text-center opacity-75 line-clamp-2",
543
539
  children: button.message
544
540
  }
545
541
  )
@@ -576,14 +572,38 @@ ${button.message || ""}`,
576
572
  var import_react2 = require("react");
577
573
 
578
574
  // src/utils/loaders.ts
579
- var NODE_ONLY_EXTENSIONS = [".sps", ".spb", ".ce"];
580
- var BROWSER_EXTENSIONS = [".obf", ".obz", ".gridset", ".plist", ".grd", ".opml", ".dot"];
575
+ var NODE_ONLY_EXTENSIONS = [".xlsx", ".xls"];
576
+ var BROWSER_EXTENSIONS = [
577
+ ".obf",
578
+ ".obz",
579
+ ".gridset",
580
+ ".plist",
581
+ ".grd",
582
+ ".opml",
583
+ ".dot",
584
+ ".sps",
585
+ ".spb",
586
+ ".ce"
587
+ ];
581
588
  function isBrowserEnvironment() {
582
589
  return typeof window !== "undefined" && typeof window.document !== "undefined";
583
590
  }
584
591
  async function importProcessors() {
592
+ if (isBrowserEnvironment()) {
593
+ return import("@willwade/aac-processors/browser");
594
+ }
585
595
  return import("@willwade/aac-processors");
586
596
  }
597
+ async function configureBrowserSqlJs(config) {
598
+ if (!isBrowserEnvironment()) {
599
+ throw new Error("configureBrowserSqlJs can only be used in a browser environment.");
600
+ }
601
+ const processors = await import("@willwade/aac-processors/browser");
602
+ if (typeof processors.configureSqlJs !== "function") {
603
+ throw new Error("configureSqlJs is not available in this build of @willwade/aac-processors.");
604
+ }
605
+ processors.configureSqlJs(config);
606
+ }
587
607
  async function getProcessorForFile(filepath, options) {
588
608
  const { getProcessor } = await importProcessors();
589
609
  const ext = filepath.toLowerCase().split(".").pop();
@@ -601,11 +621,6 @@ async function getProcessorForFile(filepath, options) {
601
621
  throw new Error(`Unsupported file type: ${extension}`);
602
622
  }
603
623
  if (extension === ".sps" || extension === ".spb") {
604
- if (isBrowserEnvironment()) {
605
- throw new Error(
606
- `SNAP files (.sps, .spb) require server-side processing. Please use the server API or upload a browser-compatible format. Browser supports: ${BROWSER_EXTENSIONS.join(", ")}`
607
- );
608
- }
609
624
  const { SnapProcessor } = await importProcessors();
610
625
  return {
611
626
  processor: options ? new SnapProcessor(null, options) : new SnapProcessor(),
@@ -659,7 +674,12 @@ async function loadAACFileFromURL(url, options) {
659
674
  return loadAACFileFromFile(blob, filename, options);
660
675
  }
661
676
  async function loadAACFileWithMetadata(input, options) {
662
- const tree = await loadAACFile(input, options);
677
+ let tree;
678
+ if (typeof input === "string") {
679
+ tree = await loadAACFile(input, options);
680
+ } else {
681
+ tree = await loadAACFile(input, options);
682
+ }
663
683
  let filepath = "";
664
684
  if (typeof input === "string") {
665
685
  filepath = input;
@@ -695,8 +715,9 @@ function getFilenameFromURL(url) {
695
715
  }
696
716
  }
697
717
  async function calculateMetrics(tree, options = {}) {
698
- const aacProcessors = await import("@willwade/aac-processors");
699
- if (!aacProcessors.MetricsCalculator) {
718
+ const aacProcessors = await importProcessors();
719
+ const hasMetricsCalculator = typeof aacProcessors.MetricsCalculator === "function";
720
+ if (!hasMetricsCalculator) {
700
721
  console.warn("MetricsCalculator not available in this environment");
701
722
  return [];
702
723
  }
@@ -751,14 +772,14 @@ function getSupportedFormats() {
751
772
  {
752
773
  name: "TD Snap",
753
774
  extensions: [".sps", ".spb"],
754
- description: "Tobii Dynavox Snap files",
755
- browserCompatible: false
775
+ description: "Tobii Dynavox Snap files (requires SQL.js in browser)",
776
+ browserCompatible: true
756
777
  },
757
778
  {
758
779
  name: "TouchChat",
759
780
  extensions: [".ce"],
760
- description: "Saltillo TouchChat files",
761
- browserCompatible: false
781
+ description: "Saltillo TouchChat files (requires SQL.js in browser)",
782
+ browserCompatible: true
762
783
  },
763
784
  {
764
785
  name: "OpenBoard",
@@ -996,6 +1017,7 @@ function useSentenceBuilder() {
996
1017
  0 && (module.exports = {
997
1018
  BoardViewer,
998
1019
  calculateMetrics,
1020
+ configureBrowserSqlJs,
999
1021
  getBrowserExtensions,
1000
1022
  getNodeOnlyExtensions,
1001
1023
  getSupportedFormats,