dock-spreadsheet 0.0.0-stage → 0.1.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/lib/index.js ADDED
@@ -0,0 +1,146 @@
1
+ import { open } from "node:fs/promises";
2
+ import { extname, isAbsolute, resolve } from "node:path";
3
+ //#region src/index.ts
4
+ const name = "dock-spreadsheet";
5
+ /**
6
+ * Services required before mounting.
7
+ *
8
+ * The declared-authority list these routes fence with used to be injected here
9
+ * as `webRuntime`. DSH renamed that service to `webStartup` in 0.2.1, so the
10
+ * list is resolved lazily through `declaredTrustedHosts` instead: a renamed
11
+ * service can no longer park this plugin, and older hosts still mount.
12
+ */
13
+ const inject = ["webServer"];
14
+ const MAX_FILE_BYTES = 20971520;
15
+ const MAX_REQUEST_BYTES = 8192;
16
+ const EXTENSIONS = /* @__PURE__ */ new Set([
17
+ ".xlsx",
18
+ ".xls",
19
+ ".ods",
20
+ ".csv",
21
+ ".tsv"
22
+ ]);
23
+ var ReadError = class extends Error {
24
+ status;
25
+ code;
26
+ constructor(status, code, message) {
27
+ super(message);
28
+ this.status = status;
29
+ this.code = code;
30
+ }
31
+ };
32
+ /**
33
+ * Authorities this deployment declared besides loopback.
34
+ *
35
+ * DSH publishes them as `trustedHosts` on `webStartup` (0.2.1+); `webRuntime`
36
+ * is the same service under its 0.1.x / 0.2.0 name, and a host that exposes
37
+ * only the connection RPC carries the list on `connection`. Probe all three
38
+ * lazily: a host with none of them keeps the loopback-only fence, and no
39
+ * rename can park this plugin on a missing service again.
40
+ */
41
+ function declaredTrustedHosts(ctx) {
42
+ if (typeof ctx.get !== "function") return [];
43
+ for (const name of [
44
+ "webStartup",
45
+ "webRuntime",
46
+ "connection"
47
+ ]) {
48
+ const declared = ctx.get(name)?.trustedHosts;
49
+ if (!Array.isArray(declared)) continue;
50
+ return declared.filter((entry) => typeof entry === "string");
51
+ }
52
+ return [];
53
+ }
54
+ function trusted(req, trustedHosts) {
55
+ const host = req.headers.host;
56
+ if (!host || req.headers["sec-fetch-site"] === "cross-site") return false;
57
+ let target;
58
+ try {
59
+ target = new URL(`http://${host}`);
60
+ } catch {
61
+ return false;
62
+ }
63
+ const octets = target.hostname.split(".");
64
+ const loopback = target.hostname === "localhost" || target.hostname === "[::1]" || octets.length === 4 && octets[0] === "127" && octets.every((part) => /^\d{1,3}$/.test(part) && Number(part) <= 255);
65
+ const allowed = trustedHosts.some((entry) => {
66
+ try {
67
+ const configured = new URL(`http://${entry}`);
68
+ return configured.host === target.host || !configured.port && configured.hostname === target.hostname;
69
+ } catch {
70
+ return false;
71
+ }
72
+ });
73
+ if (!loopback && !allowed) return false;
74
+ if (typeof req.headers.origin === "string") try {
75
+ if (new URL(req.headers.origin).host !== target.host) return false;
76
+ } catch {
77
+ return false;
78
+ }
79
+ return true;
80
+ }
81
+ function fail(res, status, code, message) {
82
+ res.writeHead(status, {
83
+ "content-type": "application/json; charset=utf-8",
84
+ "cache-control": "no-store"
85
+ });
86
+ res.end(JSON.stringify({
87
+ ok: false,
88
+ error: {
89
+ code,
90
+ message
91
+ }
92
+ }));
93
+ }
94
+ async function requestPath(req) {
95
+ let body = "";
96
+ for await (const chunk of req) {
97
+ body += Buffer.from(chunk).toString("utf8");
98
+ if (Buffer.byteLength(body) > MAX_REQUEST_BYTES) throw new ReadError(413, "too-large", "request too large");
99
+ }
100
+ let path;
101
+ try {
102
+ path = JSON.parse(body).path;
103
+ } catch {
104
+ throw new ReadError(400, "bad-request", "invalid JSON request");
105
+ }
106
+ if (typeof path !== "string" || !isAbsolute(path) || !EXTENSIONS.has(extname(path).toLowerCase())) throw new ReadError(400, "bad-request", "absolute spreadsheet path required");
107
+ return resolve(path);
108
+ }
109
+ async function readSpreadsheet(path) {
110
+ const file = await open(path, "r");
111
+ try {
112
+ const info = await file.stat();
113
+ if (!info.isFile()) throw new ReadError(400, "bad-request", "not a regular file");
114
+ if (info.size > 20971520) throw new ReadError(413, "too-large", "spreadsheet exceeds 20 MiB limit");
115
+ const bytes = await file.readFile();
116
+ if (bytes.length > 20971520) throw new ReadError(413, "too-large", "spreadsheet exceeds 20 MiB limit");
117
+ return bytes;
118
+ } finally {
119
+ await file.close();
120
+ }
121
+ }
122
+ function apply(ctx) {
123
+ ctx.effect(() => ctx.webServer.register({
124
+ kind: "prefix",
125
+ path: "/dock-spreadsheet",
126
+ handler: async (req, res) => {
127
+ if (!trusted(req, declaredTrustedHosts(ctx))) return fail(res, 403, "forbidden", "forbidden");
128
+ if (req.method !== "POST" || new URL(req.url ?? "/", "http://dsh.internal").pathname !== "/dock-spreadsheet/read") return fail(res, 404, "not-found", "unknown route");
129
+ try {
130
+ const bytes = await readSpreadsheet(await requestPath(req));
131
+ res.writeHead(200, {
132
+ "content-type": "application/octet-stream",
133
+ "content-length": bytes.length,
134
+ "cache-control": "no-store",
135
+ "x-content-type-options": "nosniff"
136
+ });
137
+ res.end(bytes);
138
+ } catch (cause) {
139
+ if (cause instanceof ReadError) fail(res, cause.status, cause.code, cause.message);
140
+ else fail(res, 400, "fs-error", cause instanceof Error ? cause.message : "read failed");
141
+ }
142
+ }
143
+ }), "dock-spreadsheet: read route");
144
+ }
145
+ //#endregion
146
+ export { MAX_FILE_BYTES, ReadError, apply, inject, name, readSpreadsheet };
@@ -0,0 +1,3 @@
1
+ import { type ReactNode } from 'react';
2
+ import type { ViewProps } from './contract.ts';
3
+ export declare function SpreadsheetView({ seed }: ViewProps): ReactNode;
@@ -0,0 +1,291 @@
1
+ /**
2
+ * Vendored copy of the dock workbench contract (AKS1st/dock, src/client/contract.ts).
3
+ *
4
+ * This plugin imports the contract types type-only; the file is copied here so
5
+ * the repo builds standalone without a package dependency on the dock base
6
+ * (`dock` is not published to npm; `link:` devDependencies would break clean
7
+ * clones). Keep this file in sync with the upstream contract when it changes.
8
+ */
9
+ /**
10
+ * Public contract of the dock base (client side).
11
+ *
12
+ * Feature plugins consume this contract via type-only import
13
+ * (`import type {} from './contract.ts'`), which also pulls
14
+ * in the `Context.workbench` augmentation below — the single restatement
15
+ * point, so plugins never re-declare the service on their own (purity gate
16
+ * friendly: type-only imports are erased at build time).
17
+ *
18
+ * Runtime collaboration happens exclusively through `ctx.workbench` method
19
+ * calls; value-importing another plugin's bundle is forbidden by the
20
+ * client-bundle purity convention.
21
+ */
22
+ import type { ComponentType, ReactNode } from 'react';
23
+ /** The cordis context face the workbench hands to view components
24
+ * (structural subset; feature plugins may extend it locally). */
25
+ export interface WorkbenchContext {
26
+ effect(fn: () => void | (() => void), label?: string): void;
27
+ get<T = unknown>(name: string): T | undefined;
28
+ on(event: string, listener: (...args: unknown[]) => void): () => void;
29
+ provide<T>(name: string, value: T): void;
30
+ [name: string]: unknown;
31
+ }
32
+ /** Props every registered view component receives. */
33
+ export interface ViewProps {
34
+ ctx: WorkbenchContext;
35
+ /** The view's registered id (`'files'`, `'my-plugin:db'`). */
36
+ viewId: string;
37
+ /** The active DSH conversation id when one is known. */
38
+ sessionId?: string;
39
+ /** Whether this view is the one on screen (inactive views may pause polling). */
40
+ active: boolean;
41
+ /** The open seed this tab was opened with (file path, title, meta). */
42
+ seed?: unknown;
43
+ }
44
+ /** A view component, or a lazy factory resolving to one on first use. */
45
+ export type ViewComponent = ComponentType<ViewProps> | (() => Promise<ComponentType<ViewProps>>);
46
+ /**
47
+ * An SVG icon spec, rendered by the dock shell with `currentColor` so it
48
+ * follows the theme. `path` is a single SVG path `d` (fill style by
49
+ * default; `stroke: true` switches to lucide-style stroke rendering).
50
+ */
51
+ export interface IconSpec {
52
+ /** SVG path `d` data (24×24 viewBox by default). */
53
+ path: string;
54
+ /** Rendered size in px; default 16. */
55
+ size?: number;
56
+ /** Override the viewBox (default '0 0 24 24'). */
57
+ viewBox?: string;
58
+ /** Stroke style instead of fill: stroke=currentColor, stroke-width 2, round caps/joins. */
59
+ stroke?: boolean;
60
+ }
61
+ /**
62
+ * What a registration may pass as an icon: any React node (emoji, custom
63
+ * component) or an SVG spec (`{ path: 'M...' }`), rendered by the shell.
64
+ */
65
+ export type IconRef = ReactNode | IconSpec;
66
+ /** Input handed to a view's `beforeClose` hook when one of its instances is about to close. */
67
+ export interface CloseRequest {
68
+ /** The view's registered id. */
69
+ viewId: string;
70
+ /** The instance being closed (shared vocabulary with editorTabs / floatingWindows). */
71
+ instanceId: string;
72
+ /** The instance's open seed — views read instance-level state (e.g. `seed.meta?.dirty`) here. */
73
+ seed?: EditorOpenSeed;
74
+ }
75
+ /** One view registered into a workbench region (side bar pane / editor area tab). */
76
+ export interface ViewDefinition {
77
+ /** Unique id; also the `viewId` handed to the component. */
78
+ id: string;
79
+ /** Title (i18n friendly: string or () => string). */
80
+ title: string | (() => string);
81
+ /** Icon shown in the activity bar / tab strip. */
82
+ icon?: IconRef;
83
+ /** Sort order (ascending); default 100. */
84
+ order?: number;
85
+ /** The component (or lazy factory). */
86
+ component: ViewComponent;
87
+ /**
88
+ * Close gate: called with the instance's `{ viewId, instanceId, seed }`
89
+ * before the shell removes its tab / floating window. Return `false` (or a
90
+ * promise resolving to `false`) to cancel the close; any other value lets
91
+ * it proceed. Views use this to confirm unsaved changes — typically read
92
+ * `seed.meta?.dirty` and `window.confirm(...)` themselves. The hook is
93
+ * definition-level (registered once with the view); per-instance state
94
+ * lives on the seed (see `updateViewSeed`).
95
+ */
96
+ beforeClose?(instance: CloseRequest): boolean | Promise<boolean>;
97
+ }
98
+ /** One activity-bar item (the left vertical strip, VSCode style). */
99
+ export interface ActivityBarItemDefinition {
100
+ id: string;
101
+ pluginId?: string;
102
+ title: string;
103
+ icon: IconRef;
104
+ /** Sort order (ascending); default 100. */
105
+ order?: number;
106
+ /** The side bar pane to reveal when this item is activated. */
107
+ paneId: string;
108
+ }
109
+ /** One status-bar item (bottom strip, left/right groups in Phase 2). */
110
+ export interface StatusBarItemDefinition {
111
+ id: string;
112
+ /** Sort order (ascending); default 100. */
113
+ order?: number;
114
+ component: ComponentType<{
115
+ ctx: WorkbenchContext;
116
+ }>;
117
+ }
118
+ /** One command, invocable through `executeCommand` (keybindings in Phase 2). */
119
+ export interface CommandDefinition {
120
+ id: string;
121
+ title: string;
122
+ run: (...args: unknown[]) => unknown | Promise<unknown>;
123
+ }
124
+ /** The screen edge the workbench docks to. */
125
+ export type DockPosition = 'left' | 'right' | 'top' | 'bottom';
126
+ /** Minimum floating-window width in px (enforced by drag math and CSS). */
127
+ export declare const FLOATING_MIN_WIDTH = 240;
128
+ /** Minimum floating-window height in px (enforced by drag math and CSS). */
129
+ export declare const FLOATING_MIN_HEIGHT = 160;
130
+ /** Floating-window title bar height in px (`.dsh-wb-floating-head`); the
131
+ * viewport clamp keeps at least this strip reachable on screen. */
132
+ export declare const FLOATING_HEAD_HEIGHT = 34;
133
+ /**
134
+ * The workbench layout snapshot: which activity is active, whether the side
135
+ * bar is open, which editor views are open (tab strip), the dock
136
+ * configuration (edge / auto-hide), and the independent floating windows.
137
+ * The workbench always runs in dock mode (macOS-like floating bar); the
138
+ * embedded panel presentation was removed.
139
+ */
140
+ export interface WorkbenchLayout {
141
+ /** Active activity-bar item id; null collapses the workbench to the strip. */
142
+ activity: string | null;
143
+ sideBarOpen: boolean;
144
+ /** Open editor view instances in the editor area (tab order). */
145
+ editorTabs: EditorTab[];
146
+ /** The focused editor instance id. */
147
+ activeEditorTab: string | null;
148
+ /** The screen edge this workbench docks to. */
149
+ dock: DockPosition;
150
+ /** Auto-hide behavior: 'off' (always visible) or 'edge' (hide when the mouse leaves). */
151
+ autoHide: 'off' | 'edge';
152
+ /** User-ordered activity items (drag-sorted; items not listed keep their registered order). */
153
+ activityOrder: string[];
154
+ /** Independent floating windows (viewId -> window). */
155
+ floatingWindows: Record<string, FloatingWindow>;
156
+ }
157
+ /** Payload an editor view carries to the component (file path, title, custom state). */
158
+ export interface EditorOpenSeed {
159
+ /** A file path the view loads (editor reads fs through its own route). */
160
+ path?: string;
161
+ /** Overrides the descriptor title (the editor tab shows the file name). */
162
+ title?: string;
163
+ /** JSON-serializable custom state carried on the tab (persisted across reloads). */
164
+ meta?: unknown;
165
+ }
166
+ /** One independent floating window (an open instance + geometry, persisted). */
167
+ export interface FloatingWindow {
168
+ /** The open instance id (shared vocabulary with editorTabs). */
169
+ instanceId: string;
170
+ viewId: string;
171
+ seed?: EditorOpenSeed;
172
+ x: number;
173
+ y: number;
174
+ width: number;
175
+ height: number;
176
+ }
177
+ /** One open editor instance (a view + its seed) hosted in the editor area. */
178
+ export interface EditorTab {
179
+ /** Unique instance id (stable across container moves, survives reloads). */
180
+ instanceId: string;
181
+ viewId: string;
182
+ seed?: EditorOpenSeed;
183
+ }
184
+ /** Options for opening a file path through the workbench (system entry). */
185
+ export interface OpenPathOptions {
186
+ /** Explicit title (defaults to the file name). */
187
+ title?: string;
188
+ /** Target editor view id (defaults to the registered default file view). */
189
+ viewId?: string;
190
+ }
191
+ /**
192
+ * The registry service published as `ctx.workbench` by the workbench base.
193
+ * Every `register*` call returns a disposer that unregisters the item; the
194
+ * consuming plugin wraps it in `ctx.effect(...)` so Cordis fiber disposal
195
+ * (HMR / disable) reverts the registration.
196
+ */
197
+ export interface PluginDefinition {
198
+ id: string;
199
+ title: string;
200
+ description?: string;
201
+ icon?: IconRef;
202
+ hasEntry: boolean;
203
+ order?: number;
204
+ }
205
+ export interface WorkbenchService {
206
+ registerPlugin(def: PluginDefinition): () => void;
207
+ registerActivityBarItem(def: ActivityBarItemDefinition): () => void;
208
+ registerPanel(def: ViewDefinition & {
209
+ region: 'sideBar';
210
+ }): () => void;
211
+ registerEditorView(def: ViewDefinition): () => void;
212
+ registerStatusBarItem(def: StatusBarItemDefinition): () => void;
213
+ registerCommand(def: CommandDefinition): () => void;
214
+ executeCommand(id: string, ...args: unknown[]): Promise<unknown>;
215
+ /** Read the current layout (synchronous snapshot). */
216
+ getLayout(): WorkbenchLayout;
217
+ /** Patch the layout (persisted to localStorage). */
218
+ updateLayout(patch: Partial<WorkbenchLayout>): void;
219
+ /** Subscribe to layout changes; returns the disposer. */
220
+ onDidChangeLayout(listener: () => void): () => void;
221
+ /**
222
+ * Open (or focus) one view instance. Defaults to the editor area (tab);
223
+ * options.floating hosts it in an independent floating window. Returns
224
+ * the instance id (a matching open focuses the existing instance).
225
+ * Replacing an existing instance's seed — opening a new file into an
226
+ * already-open window/tab — is gated by the view's `beforeClose` hook
227
+ * exactly like closing it: a `false` verdict aborts the open and keeps the
228
+ * old seed, so a dirty editor's content is never silently discarded.
229
+ */
230
+ openView(viewId: string, seed?: EditorOpenSeed, options?: {
231
+ floating?: boolean;
232
+ }): string;
233
+ /**
234
+ * Close a view instance wherever it lives (tab or floating); unknown ids
235
+ * are a no-op. Before removing the instance the shell consults the
236
+ * instance's view definition: when the view registered a `beforeClose`
237
+ * hook it is called with `{ viewId, instanceId, seed }`; returning `false`
238
+ * (or a promise resolving to `false`) cancels the close. The call is
239
+ * fire-and-forget — with an async hook the confirm dialog shows while the
240
+ * layout stays untouched, and the removal happens on approval.
241
+ */
242
+ closeViewInstance(instanceId: string): void;
243
+ /**
244
+ * Patch one open instance's seed in place (editor tab or floating window;
245
+ * unknown ids are a no-op). `patch.meta` is shallow-merged into the
246
+ * instance's current meta when both are plain objects, so an editor writes
247
+ * its dirty flag as `updateViewSeed(id, { meta: { dirty: true } })`; all
248
+ * other patch fields replace the seed's field wholesale.
249
+ */
250
+ updateViewSeed(instanceId: string, patch: Partial<EditorOpenSeed>): void;
251
+ /** Move a floating window (persisted); the resulting rect is clamped so
252
+ * the title bar stays on-screen. */
253
+ moveFloatingWindow(instanceId: string, x: number, y: number): void;
254
+ /** Resize a floating window from any edge/corner (persisted): the full
255
+ * rect is passed because dragging a west/north edge also moves the
256
+ * window. The resulting rect is clamped so the title bar stays on-screen. */
257
+ resizeFloatingWindow(instanceId: string, x: number, y: number, width: number, height: number): void;
258
+ /** Pull every open floating window back so its title bar is on-screen
259
+ * (viewports shrink, or geometry remembered on a larger screen). Drags
260
+ * already clamp live; this covers resize/mount recovery. */
261
+ clampFloatingWindowsIntoView(): void;
262
+ /**
263
+ * Unified file-path entry: system interception (chat links, produced
264
+ * files) and third-party plugins route here; the registered open-path
265
+ * handler (the file domain host, e.g. dock-files) owns the path.
266
+ */
267
+ openPath(path: string, options?: OpenPathOptions): void;
268
+ /** The file-domain host declares it can open file paths. Returns the disposer. */
269
+ registerOpenPathHandler(handler: (path: string, options?: OpenPathOptions) => void): () => void;
270
+ /** Registry lookups (undefined when not registered). */
271
+ getPanel(id: string): (ViewDefinition & {
272
+ region: 'sideBar';
273
+ }) | undefined;
274
+ getEditorView(id: string): ViewDefinition | undefined;
275
+ getActivityItem(id: string): ActivityBarItemDefinition | undefined;
276
+ /** Snapshot of all registered items (for the shell render). */
277
+ getPanels(): readonly (ViewDefinition & {
278
+ region: 'sideBar';
279
+ })[];
280
+ getEditorViews(): readonly ViewDefinition[];
281
+ getActivityItems(): readonly ActivityBarItemDefinition[];
282
+ getStatusItems(): readonly StatusBarItemDefinition[];
283
+ getCommands(): readonly CommandDefinition[];
284
+ /** Subscribe to registry changes; returns the disposer. */
285
+ subscribe(listener: () => void): () => void;
286
+ }
287
+ declare module 'cordis' {
288
+ interface Context {
289
+ workbench: WorkbenchService;
290
+ }
291
+ }
@@ -0,0 +1,3 @@
1
+ import type { WorkbenchContext } from './contract.ts';
2
+ export declare const inject: string[];
3
+ export declare function apply(ctx: WorkbenchContext): void;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * URL detection for spreadsheet cell text.
3
+ *
4
+ * Cells are rendered as plain text so workbook content can never become DOM,
5
+ * which means a URL sitting in a cell is inert until it is recognised here and
6
+ * wrapped in an anchor by the cell formatter. Detection is deliberately narrow:
7
+ * an explicit `http(s)://` scheme or a `www.` prefix. Bare domains and e-mail
8
+ * addresses are left as text because their false-positive rate on arbitrary
9
+ * tabular data is high.
10
+ */
11
+ /** One run of cell text: a URL when `url` is present, otherwise literal text. */
12
+ export interface LinkSegment {
13
+ /** The text exactly as it appears in the cell. */
14
+ text: string;
15
+ /** Absolute URL to open, or `undefined` for literal text. */
16
+ url?: string;
17
+ }
18
+ /**
19
+ * Split cell text into literal runs and detected URLs, preserving the original
20
+ * text verbatim. A candidate immediately preceded by a word character is not
21
+ * detected, so `abchttps://x` stays text.
22
+ *
23
+ * @param text - Raw cell text.
24
+ * @returns Segments in source order; a URL segment carries its absolute `url`.
25
+ */
26
+ export declare function splitLinks(text: string): LinkSegment[];
@@ -0,0 +1,2 @@
1
+ /** Viewer-only chrome layered over Tabulator's bundled stylesheet. */
2
+ export declare const viewerCss = "\n.ds-sheet {\n --ds-bg: var(--dsw-alias-fill-primary, #fff);\n --ds-surface: var(--dsw-alias-fill-control, #f8fafc);\n --ds-text: var(--dsw-alias-label-primary, #202631);\n --ds-muted: var(--dsw-alias-label-secondary, #667085);\n --ds-line: var(--dsw-alias-border-light, #dce3e8);\n --ds-accent: var(--dsw-alias-interactive-fg-accent, #1766d5);\n display: flex; flex-direction: column; height: 100%; min-height: 0;\n container: ds-viewer / inline-size;\n overflow: hidden; background: var(--ds-bg); color: var(--ds-text);\n font: 12px/1.4 system-ui, -apple-system, 'Segoe UI', sans-serif;\n}\n.ds-sheet * { box-sizing: border-box; }\n.ds-sheet-head { display: flex; align-items: center; gap: 8px; min-height: 36px; padding: 4px 12px; border-bottom: 1px solid var(--ds-line); flex: none; }\n.ds-sheet-filemark { display: grid; place-items: center; flex: none; width: 25px; height: 25px; border-radius: 5px; background: #e6f3ed; color: #14734e; font-weight: 750; font-size: 14px; }\n.ds-sheet-title { font-weight: 650; font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; flex: 1; }\n.ds-sheet-readonly { flex: none; padding: 2px 7px; border: 1px solid var(--ds-line); border-radius: 10px; color: var(--ds-muted); background: var(--ds-surface); font-size: 11px; }\n.ds-sheet-tools { display: flex; align-items: center; gap: 8px; padding: 4px 12px; min-height: 36px; border-bottom: 1px solid var(--ds-line); flex: none; }\n.ds-sheet-tool { flex: none; display: inline-flex; align-items: center; gap: 5px; min-height: 28px; padding: 4px 9px; border: 1px solid var(--ds-line); border-radius: 5px; background: var(--ds-bg); color: var(--ds-text); font: inherit; cursor: pointer; }\n.ds-sheet-tool:hover { background: var(--ds-surface); }\n.ds-sheet-tool[aria-pressed='true'] { color: var(--ds-accent); border-color: var(--ds-accent); background: var(--ds-surface); }\n.ds-sheet-tool:focus-visible, .ds-sheet-tab:focus-visible, .ds-sheet-search:focus-visible { outline: 2px solid var(--ds-accent); outline-offset: 1px; }\n.ds-sheet-search-wrap { position: relative; display: flex; align-items: center; min-width: 110px; max-width: 300px; flex: 1; }\n.ds-sheet-search-icon { position: absolute; left: 9px; color: var(--ds-muted); font-size: 15px; pointer-events: none; line-height: 1; }\n.ds-sheet-search { width: 100%; min-width: 0; height: 28px; padding: 4px 8px 4px 30px; border: 1px solid var(--ds-line); border-radius: 5px; outline: none; background: var(--ds-bg); color: var(--ds-text); font: inherit; }\n.ds-sheet-search::placeholder { color: var(--ds-muted); }\n.ds-sheet-search:disabled { opacity: .6; }\n.ds-sheet-hint { margin-left: auto; white-space: nowrap; color: var(--ds-muted); font-size: 11px; }\n.ds-sheet-inspector { display: flex; align-items: stretch; flex: none; min-height: 30px; border-bottom: 1px solid var(--ds-line); }\n.ds-sheet-address { display: grid; place-items: center; flex: none; width: 69px; padding: 4px; border-right: 1px solid var(--ds-line); color: var(--ds-accent); font-weight: 650; font-variant-numeric: tabular-nums; background: var(--ds-surface); }\n.ds-sheet-value { display: block; min-width: 0; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding: 6px 12px; color: var(--ds-text); user-select: text; }\n.ds-sheet-value.is-empty { color: var(--ds-muted); }\n.ds-sheet-grid { flex: 1; min-height: 0; overflow: hidden; }\n.ds-sheet .tabulator { border: 0; height: 100%; background: var(--ds-bg); color: var(--ds-text); font-family: inherit; font-size: 12px; }\n.ds-sheet .tabulator .tabulator-header { border: 0; border-bottom: 1px solid var(--ds-line); background: var(--ds-surface); color: var(--ds-muted); font-weight: 600; }\n.ds-sheet .tabulator .tabulator-header .tabulator-col { border-right: 1px solid var(--ds-line); background: var(--ds-surface); text-align: center; }\n.ds-sheet .tabulator .tabulator-header .tabulator-col .tabulator-col-content { padding: 7px 5px; }\n.ds-sheet .tabulator .tabulator-header .tabulator-col .tabulator-col-title { text-align: center; }\n.ds-sheet .tabulator .tabulator-header .tabulator-col.tabulator-sortable:hover { background: var(--ds-bg); }\n.ds-sheet .tabulator-row, .ds-sheet .tabulator-row:nth-child(even) { background: var(--ds-bg); color: var(--ds-text); min-height: 29px; border-bottom: 1px solid var(--ds-line); }\n.ds-sheet .tabulator-row:hover, .ds-sheet .tabulator-row:nth-child(even):hover { background: var(--ds-surface); }\n.ds-sheet .tabulator-row .tabulator-cell { height: 29px; padding: 6px 8px; border-right: 1px solid var(--ds-line); vertical-align: middle; }\n.ds-sheet .tabulator-row .tabulator-cell:first-child { position: sticky; left: 0; z-index: 2; text-align: right; font-variant-numeric: tabular-nums; color: var(--ds-muted); background: var(--ds-surface); }\n.ds-sheet .tabulator-row .tabulator-cell.ds-selected-cell { box-shadow: inset 0 0 0 2px var(--ds-accent); background: var(--ds-bg); z-index: 3; }\n.ds-sheet .tabulator .tabulator-header-filter { margin-top: 4px; }\n.ds-sheet .tabulator .tabulator-header-filter input { width: 100%; min-width: 0; padding: 3px 5px; border: 1px solid var(--ds-line); border-radius: 4px; color: var(--ds-text); background: var(--ds-bg); font: inherit; }\n.ds-sheet:not(.ds-filtering) .tabulator .tabulator-header-filter { display: none; }\n.ds-sheet .ds-url { color: var(--ds-accent); text-decoration: underline; text-underline-offset: 2px; cursor: pointer; }\n.ds-sheet .ds-url:hover { text-decoration-thickness: 2px; }\n.ds-sheet-footer { display: flex; align-items: stretch; flex: none; min-height: 32px; border-top: 1px solid var(--ds-line); background: var(--ds-surface); }\n.ds-sheet-tabs { display: flex; align-items: stretch; flex: 1; min-width: 0; overflow-x: auto; scrollbar-width: thin; padding-left: 8px; }\n.ds-sheet-tab { position: relative; flex: none; max-width: 170px; min-width: 72px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding: 6px 14px; border: 0; border-right: 1px solid var(--ds-line); background: transparent; color: var(--ds-muted); font: inherit; cursor: pointer; }\n.ds-sheet-tab:hover { background: var(--ds-bg); color: var(--ds-text); }\n.ds-sheet-tab[aria-selected='true'] { background: var(--ds-bg); color: var(--ds-accent); font-weight: 650; }\n.ds-sheet-tab[aria-selected='true']::after { content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 2px; background: var(--ds-accent); }\n.ds-sheet-status { flex: none; display: flex; align-items: center; gap: 6px; padding: 0 10px; border-left: 1px solid var(--ds-line); color: var(--ds-muted); white-space: nowrap; font-variant-numeric: tabular-nums; }\n.ds-sheet-message { padding: 16px; overflow: auto; }\n@container ds-viewer (max-width: 440px) {\n .ds-sheet-hint { display: none; }\n .ds-sheet-status { padding: 0 6px; }\n .ds-sheet-tool { padding-left: 7px; padding-right: 7px; }\n}\n@media (max-width: 520px) {\n .ds-sheet-head, .ds-sheet-tools { padding-left: 8px; padding-right: 8px; }\n .ds-sheet-hint { display: none; }\n .ds-sheet-status { padding: 0 6px; }\n .ds-sheet-tool { padding-left: 7px; padding-right: 7px; }\n}\n";
@@ -0,0 +1,2 @@
1
+ declare const _default: ".tabulator{background-color:#888;border:1px solid #999;font-size:14px;overflow:hidden;position:relative;text-align:left;-webkit-transform:translateZ(0);-moz-transform:translateZ(0);-ms-transform:translateZ(0);-o-transform:translateZ(0);transform:translateZ(0)}.tabulator[tabulator-layout=fitDataFill] .tabulator-tableholder .tabulator-table{min-width:100%}.tabulator[tabulator-layout=fitDataTable]{display:inline-block}.tabulator.tabulator-block-select,.tabulator.tabulator-ranges .tabulator-cell:not(.tabulator-editing){user-select:none}.tabulator .tabulator-header{background-color:#e6e6e6;border-bottom:1px solid #999;box-sizing:border-box;color:#555;font-weight:700;outline:none;overflow:hidden;position:relative;-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none;white-space:nowrap;width:100%}.tabulator .tabulator-header.tabulator-header-hidden{display:none}.tabulator .tabulator-header .tabulator-header-contents{overflow:hidden;position:relative}.tabulator .tabulator-header .tabulator-header-contents .tabulator-headers{display:inline-block}.tabulator .tabulator-header .tabulator-col{background:#e6e6e6;border-right:1px solid #aaa;box-sizing:border-box;display:inline-flex;flex-direction:column;justify-content:flex-start;overflow:hidden;position:relative;text-align:left;vertical-align:bottom}.tabulator .tabulator-header .tabulator-col.tabulator-moving{background:#cdcdcd;border:1px solid #999;pointer-events:none;position:absolute}.tabulator .tabulator-header .tabulator-col.tabulator-range-highlight{background-color:#d6d6d6;color:#000}.tabulator .tabulator-header .tabulator-col.tabulator-range-selected{background-color:#3876ca;color:#fff}.tabulator .tabulator-header .tabulator-col .tabulator-col-content{box-sizing:border-box;padding:4px;position:relative}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-header-popup-button{padding:0 8px}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-header-popup-button:hover{cursor:pointer;opacity:.6}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title-holder{position:relative}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title{box-sizing:border-box;overflow:hidden;text-overflow:ellipsis;vertical-align:bottom;white-space:nowrap;width:100%}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title.tabulator-col-title-wrap{text-overflow:clip;white-space:normal}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title .tabulator-title-editor{background:#fff;border:1px solid #999;box-sizing:border-box;padding:1px;width:100%}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title .tabulator-header-popup-button+.tabulator-title-editor{width:calc(100% - 22px)}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-sorter{align-items:center;bottom:0;display:flex;position:absolute;right:4px;top:0}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-sorter .tabulator-arrow{border-bottom:6px solid #bbb;border-left:6px solid transparent;border-right:6px solid transparent;height:0;width:0}.tabulator .tabulator-header .tabulator-col.tabulator-col-group .tabulator-col-group-cols{border-top:1px solid #aaa;display:flex;margin-right:-1px;overflow:hidden;position:relative}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter{box-sizing:border-box;margin-top:2px;position:relative;text-align:center;width:100%}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter textarea{height:auto!important}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter svg{margin-top:3px}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter input::-ms-clear{height:0;width:0}.tabulator .tabulator-header .tabulator-col.tabulator-sortable .tabulator-col-title{padding-right:25px}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-header .tabulator-col.tabulator-sortable.tabulator-col-sorter-element:hover{background-color:#cdcdcd;cursor:pointer}}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=none] .tabulator-col-content .tabulator-col-sorter{color:#bbb}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=none] .tabulator-col-content .tabulator-col-sorter.tabulator-col-sorter-element .tabulator-arrow:hover{border-bottom:6px solid #555;cursor:pointer}}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=none] .tabulator-col-content .tabulator-col-sorter .tabulator-arrow{border-bottom:6px solid #bbb;border-top:none}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=ascending] .tabulator-col-content .tabulator-col-sorter{color:#666}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=ascending] .tabulator-col-content .tabulator-col-sorter.tabulator-col-sorter-element .tabulator-arrow:hover{border-bottom:6px solid #555;cursor:pointer}}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=ascending] .tabulator-col-content .tabulator-col-sorter .tabulator-arrow{border-bottom:6px solid #666;border-top:none}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=descending] .tabulator-col-content .tabulator-col-sorter{color:#666}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=descending] .tabulator-col-content .tabulator-col-sorter.tabulator-col-sorter-element .tabulator-arrow:hover{border-top:6px solid #555;cursor:pointer}}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=descending] .tabulator-col-content .tabulator-col-sorter .tabulator-arrow{border-bottom:none;border-top:6px solid #666;color:#666}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical .tabulator-col-content .tabulator-col-title{align-items:center;display:flex;justify-content:center;text-orientation:mixed;writing-mode:vertical-rl}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-col-vertical-flip .tabulator-col-title{transform:rotate(180deg)}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-sortable .tabulator-col-title{padding-right:0;padding-top:20px}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-sortable.tabulator-col-vertical-flip .tabulator-col-title{padding-bottom:20px;padding-right:0}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-sortable .tabulator-col-sorter{bottom:auto;justify-content:center;left:0;right:0;top:4px}.tabulator .tabulator-header .tabulator-frozen{left:0;position:sticky;z-index:11}.tabulator .tabulator-header .tabulator-frozen.tabulator-frozen-left{border-right:2px solid #aaa}.tabulator .tabulator-header .tabulator-frozen.tabulator-frozen-right{border-left:2px solid #aaa}.tabulator .tabulator-header .tabulator-calcs-holder{background:#f3f3f3!important;border-bottom:1px solid #aaa;border-top:1px solid #aaa;box-sizing:border-box;display:inline-block}.tabulator .tabulator-header .tabulator-calcs-holder .tabulator-row{background:#f3f3f3!important}.tabulator .tabulator-header .tabulator-calcs-holder .tabulator-row .tabulator-col-resize-handle{display:none}.tabulator .tabulator-header .tabulator-frozen-rows-holder{display:inline-block}.tabulator .tabulator-header .tabulator-frozen-rows-holder:empty{display:none}.tabulator .tabulator-tableholder{-webkit-overflow-scrolling:touch;overflow:auto;position:relative;white-space:nowrap;width:100%}.tabulator .tabulator-tableholder:focus{outline:none}.tabulator .tabulator-tableholder .tabulator-placeholder{align-items:center;box-sizing:border-box;display:flex;justify-content:center;min-width:100%;width:100%}.tabulator .tabulator-tableholder .tabulator-placeholder[tabulator-render-mode=virtual]{min-height:100%}.tabulator .tabulator-tableholder .tabulator-placeholder .tabulator-placeholder-contents{color:#ccc;display:inline-block;font-size:20px;font-weight:700;padding:10px;text-align:center;white-space:normal}.tabulator .tabulator-tableholder .tabulator-table{background-color:#fff;color:#333;display:inline-block;overflow:visible;position:relative;white-space:nowrap}.tabulator .tabulator-tableholder .tabulator-table .tabulator-row.tabulator-calcs{background:#e2e2e2!important;font-weight:700}.tabulator .tabulator-tableholder .tabulator-table .tabulator-row.tabulator-calcs.tabulator-calcs-top{border-bottom:2px solid #aaa}.tabulator .tabulator-tableholder .tabulator-table .tabulator-row.tabulator-calcs.tabulator-calcs-bottom{border-top:2px solid #aaa}.tabulator .tabulator-tableholder .tabulator-range-overlay{inset:0;pointer-events:none;position:absolute;z-index:11}.tabulator .tabulator-tableholder .tabulator-range-overlay .tabulator-range{border:1px solid #2975dd;box-sizing:border-box;position:absolute}.tabulator .tabulator-tableholder .tabulator-range-overlay .tabulator-range.tabulator-range-fill-preview{border-style:dashed}.tabulator .tabulator-tableholder .tabulator-range-overlay .tabulator-range-cell-active{border:2px solid #2975dd;box-sizing:border-box;position:absolute}.tabulator .tabulator-footer{background-color:#e6e6e6;border-top:1px solid #999;color:#555;font-weight:700;user-select:none;-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none;white-space:nowrap}.tabulator .tabulator-footer .tabulator-footer-contents{align-items:center;display:flex;flex-direction:row;justify-content:space-between;padding:5px 10px}.tabulator .tabulator-footer .tabulator-footer-contents:empty{display:none}.tabulator .tabulator-footer .tabulator-spreadsheet-tabs{margin-top:-5px;overflow-x:auto}.tabulator .tabulator-footer .tabulator-spreadsheet-tabs .tabulator-spreadsheet-tab{border:1px solid #999;border-bottom-left-radius:5px;border-bottom-right-radius:5px;border-top:none;display:inline-block;font-size:.9em;padding:5px}.tabulator .tabulator-footer .tabulator-spreadsheet-tabs .tabulator-spreadsheet-tab:hover{cursor:pointer;opacity:.7}.tabulator .tabulator-footer .tabulator-spreadsheet-tabs .tabulator-spreadsheet-tab.tabulator-spreadsheet-tab-active{background:#fff}.tabulator .tabulator-footer .tabulator-calcs-holder{background:#f3f3f3!important;border-bottom:1px solid #aaa;border-top:1px solid #aaa;box-sizing:border-box;overflow:hidden;text-align:left;width:100%}.tabulator .tabulator-footer .tabulator-calcs-holder .tabulator-row{background:#f3f3f3!important;display:inline-block}.tabulator .tabulator-footer .tabulator-calcs-holder .tabulator-row .tabulator-col-resize-handle{display:none}.tabulator .tabulator-footer .tabulator-calcs-holder:only-child{border-bottom:none;margin-bottom:-5px}.tabulator .tabulator-footer>*+.tabulator-page-counter{margin-left:10px}.tabulator .tabulator-footer .tabulator-page-counter{font-weight:400}.tabulator .tabulator-footer .tabulator-paginator{color:#555;flex:1;font-family:inherit;font-size:inherit;font-weight:inherit;text-align:right}.tabulator .tabulator-footer .tabulator-page-size{border:1px solid #aaa;border-radius:3px;display:inline-block;margin:0 5px;padding:2px 5px}.tabulator .tabulator-footer .tabulator-pages{margin:0 7px}.tabulator .tabulator-footer .tabulator-page{background:hsla(0,0%,100%,.2);border:1px solid #aaa;border-radius:3px;display:inline-block;margin:0 2px;padding:2px 5px}.tabulator .tabulator-footer .tabulator-page.active{color:#d00}.tabulator .tabulator-footer .tabulator-page:disabled{opacity:.5}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-footer .tabulator-page:not(disabled):hover{background:rgba(0,0,0,.2);color:#fff;cursor:pointer}}.tabulator .tabulator-col-resize-handle{display:inline-block;margin-left:-3px;margin-right:-3px;position:relative;vertical-align:middle;width:6px;z-index:11}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-col-resize-handle:hover{cursor:ew-resize}}.tabulator .tabulator-col-resize-handle:last-of-type{margin-right:0;width:3px}.tabulator .tabulator-col-resize-guide{background-color:#999;height:100%;margin-left:-.5px;opacity:.5;position:absolute;top:0;width:4px}.tabulator .tabulator-row-resize-guide{background-color:#999;height:4px;left:0;margin-top:-.5px;opacity:.5;position:absolute;width:100%}.tabulator .tabulator-alert{align-items:center;background:rgba(0,0,0,.4);display:flex;height:100%;left:0;position:absolute;text-align:center;top:0;width:100%;z-index:100}.tabulator .tabulator-alert .tabulator-alert-msg{background:#fff;border-radius:10px;display:inline-block;font-size:16px;font-weight:700;margin:0 auto;padding:10px 20px}.tabulator .tabulator-alert .tabulator-alert-msg.tabulator-alert-state-msg{border:4px solid #333;color:#000}.tabulator .tabulator-alert .tabulator-alert-msg.tabulator-alert-state-error{border:4px solid #d00;color:#590000}.tabulator-row{background-color:#fff;box-sizing:border-box;min-height:22px;position:relative}.tabulator-row.tabulator-row-even{background-color:#efefef}@media (hover:hover) and (pointer:fine){.tabulator-row.tabulator-selectable:hover{background-color:#bbb;cursor:pointer}}.tabulator-row.tabulator-selected{background-color:#9abcea}@media (hover:hover) and (pointer:fine){.tabulator-row.tabulator-selected:hover{background-color:#769bcc;cursor:pointer}}.tabulator-row.tabulator-row-moving{background:#fff;border:1px solid #000}.tabulator-row.tabulator-moving{border-bottom:1px solid #aaa;border-top:1px solid #aaa;pointer-events:none;position:absolute;z-index:15}.tabulator-row.tabulator-range-highlight .tabulator-cell.tabulator-range-row-header{background-color:#d6d6d6;color:#000}.tabulator-row.tabulator-range-highlight.tabulator-range-selected .tabulator-cell.tabulator-range-row-header,.tabulator-row.tabulator-range-selected .tabulator-cell.tabulator-range-row-header{background-color:#3876ca;color:#fff}.tabulator-row .tabulator-row-resize-handle{bottom:0;height:5px;left:0;position:absolute;right:0}.tabulator-row .tabulator-row-resize-handle.prev{bottom:auto;top:0}@media (hover:hover) and (pointer:fine){.tabulator-row .tabulator-row-resize-handle:hover{cursor:ns-resize}}.tabulator-row .tabulator-responsive-collapse{border-bottom:1px solid #aaa;border-top:1px solid #aaa;box-sizing:border-box;padding:5px}.tabulator-row .tabulator-responsive-collapse:empty{display:none}.tabulator-row .tabulator-responsive-collapse table{font-size:14px}.tabulator-row .tabulator-responsive-collapse table tr td{position:relative}.tabulator-row .tabulator-responsive-collapse table tr td:first-of-type{padding-right:10px}.tabulator-row .tabulator-cell{border-right:1px solid #aaa;box-sizing:border-box;display:inline-block;outline:none;overflow:hidden;padding:4px;position:relative;text-overflow:ellipsis;vertical-align:middle;white-space:nowrap}.tabulator-row .tabulator-cell .tabulator-tick{fill:#2dc214}.tabulator-row .tabulator-cell .tabulator-cross{fill:#ce1515}.tabulator-row .tabulator-cell.tabulator-row-header{background:#e6e6e6;border-bottom:1px solid #aaa;border-right:1px solid #999}.tabulator-row .tabulator-cell.tabulator-frozen{background-color:inherit;display:inline-block;left:0;position:sticky;z-index:11}.tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-left{border-right:2px solid #aaa}.tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-right{border-left:2px solid #aaa}.tabulator-row .tabulator-cell.tabulator-editing{border:1px solid #1d68cd;outline:none;padding:0}.tabulator-row .tabulator-cell.tabulator-editing input,.tabulator-row .tabulator-cell.tabulator-editing select{background:transparent;border:1px;outline:none}.tabulator-row .tabulator-cell.tabulator-validation-fail{border:1px solid #d00}.tabulator-row .tabulator-cell.tabulator-validation-fail input,.tabulator-row .tabulator-cell.tabulator-validation-fail select{background:transparent;border:1px;color:#d00}.tabulator-row .tabulator-cell.tabulator-row-handle{align-items:center;display:inline-flex;justify-content:center;-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none}.tabulator-row .tabulator-cell.tabulator-row-handle .tabulator-row-handle-box{width:80%}.tabulator-row .tabulator-cell.tabulator-row-handle .tabulator-row-handle-box .tabulator-row-handle-bar{background:#666;height:3px;margin-top:2px;width:100%}.tabulator-row .tabulator-cell.tabulator-range-selected:not(.tabulator-range-only-cell-selected):not(.tabulator-range-row-header){background-color:#9abcea}.tabulator-row .tabulator-cell .tabulator-data-tree-branch-empty{display:inline-block;width:7px}.tabulator-row .tabulator-cell .tabulator-data-tree-branch{border-bottom:2px solid #aaa;border-bottom-left-radius:1px;border-left:2px solid #aaa;display:inline-block;height:9px;margin-right:5px;margin-top:-9px;vertical-align:middle;width:7px}.tabulator-row .tabulator-cell .tabulator-data-tree-control{align-items:center;background:rgba(0,0,0,.1);border:1px solid #333;border-radius:2px;display:inline-flex;height:11px;justify-content:center;margin-right:5px;overflow:hidden;vertical-align:middle;width:11px}@media (hover:hover) and (pointer:fine){.tabulator-row .tabulator-cell .tabulator-data-tree-control:hover{background:rgba(0,0,0,.2);cursor:pointer}}.tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-collapse{background:transparent;display:inline-block;height:7px;position:relative;width:1px}.tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-collapse:after{background:#333;content:\"\";height:1px;left:-3px;position:absolute;top:3px;width:7px}.tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-expand{background:#333;display:inline-block;height:7px;position:relative;width:1px}.tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-expand:after{background:#333;content:\"\";height:1px;left:-3px;position:absolute;top:3px;width:7px}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle{align-items:center;background:#666;border-radius:20px;color:#fff;display:inline-flex;font-size:1.1em;font-weight:700;height:15px;justify-content:center;-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none;width:15px}@media (hover:hover) and (pointer:fine){.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle:hover{cursor:pointer;opacity:.7}}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle.open .tabulator-responsive-collapse-toggle-close{display:initial}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle.open .tabulator-responsive-collapse-toggle-open{display:none}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle svg{stroke:#fff}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle .tabulator-responsive-collapse-toggle-close{display:none}.tabulator-row .tabulator-cell .tabulator-traffic-light{border-radius:14px;display:inline-block;height:14px;width:14px}.tabulator-row.tabulator-group{background:#ccc;border-bottom:1px solid #999;border-right:1px solid #aaa;border-top:1px solid #999;box-sizing:border-box;font-weight:700;min-width:100%;padding:5px 5px 5px 10px}@media (hover:hover) and (pointer:fine){.tabulator-row.tabulator-group:hover{background-color:rgba(0,0,0,.1);cursor:pointer}}.tabulator-row.tabulator-group.tabulator-group-visible .tabulator-arrow{border-bottom:0;border-left:6px solid transparent;border-right:6px solid transparent;border-top:6px solid #666;margin-right:10px}.tabulator-row.tabulator-group.tabulator-group-level-1{padding-left:30px}.tabulator-row.tabulator-group.tabulator-group-level-2{padding-left:50px}.tabulator-row.tabulator-group.tabulator-group-level-3{padding-left:70px}.tabulator-row.tabulator-group.tabulator-group-level-4{padding-left:90px}.tabulator-row.tabulator-group.tabulator-group-level-5{padding-left:110px}.tabulator-row.tabulator-group .tabulator-group-toggle{display:inline-block}.tabulator-row.tabulator-group .tabulator-arrow{border-bottom:6px solid transparent;border-left:6px solid #666;border-right:0;border-top:6px solid transparent;display:inline-block;height:0;margin-right:16px;vertical-align:middle;width:0}.tabulator-row.tabulator-group span{color:#d00;margin-left:10px}.tabulator-range-fill-handle{background-color:#2975dd;border-radius:999px;bottom:-3px;cursor:crosshair;height:6px;pointer-events:auto;position:absolute;right:-3px;width:6px}.tabulator-toggle{background:#dcdcdc;border:1px solid #ccc;box-sizing:border-box;display:flex;flex-direction:row}.tabulator-toggle.tabulator-toggle-on{background:#1c6cc2}.tabulator-toggle .tabulator-toggle-switch{background:#fff;border:1px solid #ccc;box-sizing:border-box}.tabulator-popup-container{-webkit-overflow-scrolling:touch;background:#fff;border:1px solid #aaa;box-shadow:0 0 5px 0 rgba(0,0,0,.2);box-sizing:border-box;display:inline-block;font-size:14px;overflow-y:auto;position:absolute;z-index:10000}.tabulator-popup{border-radius:3px;padding:5px}.tabulator-tooltip{border-radius:2px;box-shadow:none;font-size:12px;max-width:min(500px,100%);padding:3px 5px;pointer-events:none}.tabulator-menu .tabulator-menu-item{box-sizing:border-box;padding:5px 10px;position:relative;user-select:none}.tabulator-menu .tabulator-menu-item.tabulator-menu-item-disabled{opacity:.5}@media (hover:hover) and (pointer:fine){.tabulator-menu .tabulator-menu-item:not(.tabulator-menu-item-disabled):hover{background:#efefef;cursor:pointer}}.tabulator-menu .tabulator-menu-item.tabulator-menu-item-submenu{padding-right:25px}.tabulator-menu .tabulator-menu-item.tabulator-menu-item-submenu:after{border-color:#aaa;border-style:solid;border-width:1px 1px 0 0;content:\"\";display:inline-block;height:7px;position:absolute;right:10px;top:calc(5px + .4em);transform:rotate(45deg);vertical-align:top;width:7px}.tabulator-menu .tabulator-menu-separator{border-top:1px solid #aaa}.tabulator-edit-list{-webkit-overflow-scrolling:touch;font-size:14px;max-height:200px;overflow-y:auto}.tabulator-edit-list .tabulator-edit-list-item{color:#333;outline:none;padding:4px}.tabulator-edit-list .tabulator-edit-list-item.active{background:#1d68cd;color:#fff}.tabulator-edit-list .tabulator-edit-list-item.active.focused{outline:1px solid hsla(0,0%,100%,.5)}.tabulator-edit-list .tabulator-edit-list-item.focused{outline:1px solid #1d68cd}@media (hover:hover) and (pointer:fine){.tabulator-edit-list .tabulator-edit-list-item:hover{background:#1d68cd;color:#fff;cursor:pointer}}.tabulator-edit-list .tabulator-edit-list-placeholder{color:#333;padding:4px;text-align:center}.tabulator-edit-list .tabulator-edit-list-group{border-bottom:1px solid #aaa;color:#333;font-weight:700;padding:6px 4px 4px}.tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-2,.tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-2{padding-left:12px}.tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-3,.tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-3{padding-left:20px}.tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-4,.tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-4{padding-left:28px}.tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-5,.tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-5{padding-left:36px}.tabulator.tabulator-ltr{direction:ltr}.tabulator.tabulator-rtl{direction:rtl;text-align:initial}.tabulator.tabulator-rtl .tabulator-header .tabulator-col{border-left:1px solid #aaa;border-right:initial;text-align:initial}.tabulator.tabulator-rtl .tabulator-header .tabulator-col.tabulator-col-group .tabulator-col-group-cols{margin-left:-1px;margin-right:0}.tabulator.tabulator-rtl .tabulator-header .tabulator-col.tabulator-sortable .tabulator-col-title{padding-left:25px;padding-right:0}.tabulator.tabulator-rtl .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-sorter{left:8px;right:auto}.tabulator.tabulator-rtl .tabulator-range-fill-handle{left:-3px;right:auto}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell{border-left:1px solid #aaa;border-right:initial}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell .tabulator-data-tree-branch{border-bottom-left-radius:0;border-bottom-right-radius:1px;border-left:initial;border-right:2px solid #aaa;margin-left:5px;margin-right:0}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell .tabulator-data-tree-control{margin-left:5px;margin-right:0}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-left{border-left:2px solid #aaa}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-right{border-right:2px solid #aaa}.tabulator.tabulator-rtl .tabulator-row .tabulator-col-resize-handle:last-of-type{margin-left:0;margin-right:-3px;width:3px}.tabulator.tabulator-rtl .tabulator-footer .tabulator-calcs-holder{text-align:initial}.tabulator-print-fullscreen{bottom:0;left:0;position:absolute;right:0;top:0;z-index:10000}body.tabulator-print-fullscreen-hide>:not(.tabulator-print-fullscreen){display:none!important}.tabulator-print-table{border-collapse:collapse}.tabulator-print-table .tabulator-data-tree-branch{border-bottom:2px solid #aaa;border-bottom-left-radius:1px;border-left:2px solid #aaa;display:inline-block;height:9px;margin-right:5px;margin-top:-9px;vertical-align:middle;width:7px}.tabulator-print-table .tabulator-print-table-group{background:#ccc;border-bottom:1px solid #999;border-right:1px solid #aaa;border-top:1px solid #999;box-sizing:border-box;font-weight:700;min-width:100%;padding:5px 5px 5px 10px}@media (hover:hover) and (pointer:fine){.tabulator-print-table .tabulator-print-table-group:hover{background-color:rgba(0,0,0,.1);cursor:pointer}}.tabulator-print-table .tabulator-print-table-group.tabulator-group-visible .tabulator-arrow{border-bottom:0;border-left:6px solid transparent;border-right:6px solid transparent;border-top:6px solid #666;margin-right:10px}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-1 td{padding-left:30px!important}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-2 td{padding-left:50px!important}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-3 td{padding-left:70px!important}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-4 td{padding-left:90px!important}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-5 td{padding-left:110px!important}.tabulator-print-table .tabulator-print-table-group .tabulator-group-toggle{display:inline-block}.tabulator-print-table .tabulator-print-table-group .tabulator-arrow{border-bottom:6px solid transparent;border-left:6px solid #666;border-right:0;border-top:6px solid transparent;display:inline-block;height:0;margin-right:16px;vertical-align:middle;width:0}.tabulator-print-table .tabulator-print-table-group span{color:#d00;margin-left:10px}.tabulator-print-table .tabulator-data-tree-control{align-items:center;background:rgba(0,0,0,.1);border:1px solid #333;border-radius:2px;display:inline-flex;height:11px;justify-content:center;margin-right:5px;overflow:hidden;vertical-align:middle;width:11px}@media (hover:hover) and (pointer:fine){.tabulator-print-table .tabulator-data-tree-control:hover{background:rgba(0,0,0,.2);cursor:pointer}}.tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-collapse{background:transparent;display:inline-block;height:7px;position:relative;width:1px}.tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-collapse:after{background:#333;content:\"\";height:1px;left:-3px;position:absolute;top:3px;width:7px}.tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-expand{background:#333;display:inline-block;height:7px;position:relative;width:1px}.tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-expand:after{background:#333;content:\"\";height:1px;left:-3px;position:absolute;top:3px;width:7px}\n/*# sourceMappingURL=tabulator.min.css.map */";
2
+ export default _default;
@@ -0,0 +1,12 @@
1
+ import { type WorkBook } from 'xlsx';
2
+ export declare const MAX_ROWS = 5000;
3
+ export declare const MAX_COLUMNS = 256;
4
+ export interface GridSheet {
5
+ name: string;
6
+ columns: string[];
7
+ rows: Record<string, string | number>[];
8
+ truncated: boolean;
9
+ }
10
+ export declare function parseWorkbook(bytes: ArrayBuffer | Uint8Array, filename?: string): WorkBook;
11
+ /** Preserve spreadsheet coordinates: first row is data, never inferred headers. */
12
+ export declare function gridSheet(book: WorkBook, name: string): GridSheet;
@@ -0,0 +1,38 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ export declare const name = "dock-spreadsheet";
3
+ /**
4
+ * Services required before mounting.
5
+ *
6
+ * The declared-authority list these routes fence with used to be injected here
7
+ * as `webRuntime`. DSH renamed that service to `webStartup` in 0.2.1, so the
8
+ * list is resolved lazily through `declaredTrustedHosts` instead: a renamed
9
+ * service can no longer park this plugin, and older hosts still mount.
10
+ */
11
+ export declare const inject: string[];
12
+ export declare const MAX_FILE_BYTES: number;
13
+ interface Context {
14
+ webServer: {
15
+ register(options: {
16
+ kind: 'prefix';
17
+ path: string;
18
+ handler: (req: IncomingMessage, res: ServerResponse) => Promise<void>;
19
+ }): () => void;
20
+ };
21
+ /**
22
+ * Read a service without declaring a hard dependency on it.
23
+ *
24
+ * Used for the optional `webStartup` / `webRuntime` / `connection` services:
25
+ * a host that provides none of them keeps the loopback-only fence instead of
26
+ * failing to mount.
27
+ */
28
+ get?(name: string): unknown;
29
+ effect(fn: () => void | (() => void), label?: string): void;
30
+ }
31
+ export declare class ReadError extends Error {
32
+ readonly status: number;
33
+ readonly code: string;
34
+ constructor(status: number, code: string, message: string);
35
+ }
36
+ export declare function readSpreadsheet(path: string): Promise<Buffer>;
37
+ export declare function apply(ctx: Context): void;
38
+ export {};