@alwith/module-editor
Document state, saving, tabs and extensible previews. The package does not depend on the file-tree package, a session store or Tauri.
import { createEditorController, EditorWorkbench, imagePreview } from "@alwith/module-editor"
import "@alwith/module-editor/styles.css"
const editor = createEditorController({ fs, confirmClose, reportError })
await editor.open("/project/readme.md")
<EditorWorkbench controller={editor} previews={[imagePreview]}
onError={reportError} />
Lifecycle and safety
Stable document IDs survive path changes. One FS instance serializes saves, moves, deletions, closes and reloads. Directory moves rebase descendant paths. Dirty documents veto deletion when confirmation returns cancel. Approved deletions temporarily lock their documents until the native operation finishes or fails. Custom engines must respect readOnly. Failed saves keep dirty state; a disk version mismatch never silently overwrites the disk. Editing while a save/reload is awaiting I/O cannot silently close or replace the newer edit.
Call requestClose(id) to close a tab and prepareShutdown() before leaving the workspace/app. Both can return false. dispose() releases subscriptions only; it does not ask, save or discard on the user's behalf. The host is responsible for crash recovery/draft persistence if required; this release retains drafts in memory while switching projects, not across process crashes.
UTF-8 and BOM-marked UTF-16 LE/BE are decoded strictly. Invalid bytes remain binary/read-only. Saving retains BOM and a document's newline convention. Text is not guessed from malformed bytes. The default file size limit is 16 MiB; pass maxFileBytes for adapters that support a different limit. Clean documents have an LRU limit (default 12); dirty documents are not evicted.
saveAs uses create-only writes. The host obtains a destination and grants access; it must not assume an arbitrary path is writable. onOpenExternal is a host action for formats that need another application.
Rendering and extensions
/core contains no React. Root exports EditorWorkbench, PreviewProvider, TextEditorProps and imagePreview. Previews are ordered: the first provider whose supports(document) returns true renders the document. Text files can switch between edit and preview. SVG/HTML are not executed by the built-in image preview. HTML/PDF/office/media services belong in explicit optional providers with the required sandbox and resource disposal; unsupported binaries are not decoded as editable text.
The default text renderer is a textarea. /monaco exports createMonacoEngine(monaco, controller): Monaco is an optional peer and never loaded by the default entry. The host imports Monaco lazily and configures workers. The engine retains undo/models and view state by document ID, releases closed models and provides dispose. The host supplies light/dark theme. U uses Monaco for code/Markdown editing and injects its shared Markdown renderer for preview.
All toolbar text is configured by EditorLabels; theme colors use semantic CSS variables. Hosts may provide renderTextEditor, requestSaveAs, onOpenExternal, and arbitrary preview providers. Business HTML/Drive/account/session integrations stay in the host.
See examples/files/editor-only.tsx and combined.tsx for independent and composed consumers.
compactToolbar keeps the file path visible and moves editor actions into an
overflow menu; actionsLabel localizes its trigger. The default toolbar remains
unchanged for other hosts. Tabs accept trackpad and wheel scrolling with hidden
scrollbars; activating a document brings its tab into view. Preview scrollbars
use thin, low-contrast thumbs based on the host foreground token.
Optional local previews
@alwith/module-editor/previews exports createPreviewProviders(host). Load this entry lazily and import @alwith/module-editor/previews/styles.css alongside the workbench stylesheet. PDF, OOXML Word, spreadsheets, Markdown, HTML, audio/video and TIFF decoders load independently when their provider is selected. These portable viewers are reduced from ALwith Desktop; account, collaboration and native filesystem integrations remain in the host.
Spreadsheet previews require SheetJS CE 0.20.3 or a compatible newer 0.20.x release. The public npm xlsx package remains on the old 0.18.5 distribution; install from the official SheetJS distribution and commit the package-manager lockfile:
bun add xlsx@https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz
The host supplies pdfWorkerSrc and resolveResource(documentPath, reference), returning { bytes, mimeType, path }. Resolve references against the supplied base path, return a canonical resource path and enforce the host's filesystem permissions. parseSpreadsheet(bytes, signal) can delegate SheetJS decoding to a worker; honor cancellation by terminating or cancelling the worker. labels and imageLabels localize controls. openExternal handles legacy .doc/.dot files and unsupported media codecs.
HTML uses an opaque sandbox with scripts enabled and no parent access, navigation permissions or network requests. Relative stylesheets, CSS assets and literal JavaScript imports are resolved before rendering. Remote HTTPS assets require explicit allowRemoteResources: true; privileged schemes and plain HTTP stay blocked. Computed resource paths and circular local script dependencies cannot be resolved ahead of rendering. Preview-owned object URLs are released on replacement/unmount; resource loading and generated content are bounded.
Markdown uses Desktop's Milkdown CommonMark/GFM presets with an outline and formatting controls. It publishes edits synchronously through the workbench's document context and never writes files directly. Programmatic replacements retain their clean state. Set autoSaveDelay to debounce the host's save action, or leave it unset for manual saving. The host remains authoritative for read-only state, dirty state, save failures and external changes.
Native spreadsheet parsing
The Tauri plugin alwith-editor (crates.io) accepts host-authorized file
bytes and forwards parsing to the private native editor library, which ships in this
package under native/<os>-<cpu>/. Stage it at build time with stageNativeModule from
@alwith/module-desktop-shell/stage, register the plugin with .plugin(alwith_editor::init(Config { development_directory })),
grant alwith-editor:default, and call it through @alwith/module-editor/commands. calamine
and the parser implementation are built into the native library; no additional process
is introduced.
parse_spreadsheet(bytes, extension) returns typed worksheets with absolute cell
coordinates, deduplicated styles, merged cells, numeric values and date epoch.
Desktop and U use this same parser. XLSX styles include fonts, colors, fills,
borders, alignment and number format codes. Other formats use calamine's available
metadata; CSV remains text. Input is capped at 16 MiB; preview limits are 2,000
rows and 200 columns and sheets report truncated when exceeded. Their tabs visibly identify a partial
preview and its limits.
/spreadsheet exports the shared SpreadsheetView and workbookSheets adapter.
Number formats use SheetJS SSF (including the 1904 epoch), not a custom formatter.
The canvas renderer supports left/center/right and top/middle/bottom alignment;
unsupported alignments report an error instead of silently dropping formatting.
Pattern fills, diagonal borders, text rotation, conditional formatting and original
row/column dimensions are not reproduced by this renderer. Dashed border subtypes
use the renderer's dashed line; this is a read-only preview, not pixel-identical Excel.