grist-widget-sdk 0.3.27 → 0.4.0

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 (86) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/README.md +1 -1
  3. package/dist/app.cjs +1139 -4336
  4. package/dist/app.cjs.map +1 -1
  5. package/dist/app.d.cts +1 -3
  6. package/dist/app.d.ts +1 -3
  7. package/dist/app.js +3 -7
  8. package/dist/chunk-4ZGVKGDF.js +204 -0
  9. package/dist/chunk-4ZGVKGDF.js.map +1 -0
  10. package/dist/{chunk-YQNMIL33.js → chunk-B5ZZNLVS.js} +528 -181
  11. package/dist/chunk-B5ZZNLVS.js.map +1 -0
  12. package/dist/chunk-O7NY46QY.js +1830 -0
  13. package/dist/chunk-O7NY46QY.js.map +1 -0
  14. package/dist/{chunk-6Y2IONGJ.js → chunk-P6VTEKWO.js} +113 -14
  15. package/dist/chunk-P6VTEKWO.js.map +1 -0
  16. package/dist/emulator/testing.cjs +1504 -4822
  17. package/dist/emulator/testing.cjs.map +1 -1
  18. package/dist/emulator/testing.d.cts +3 -6
  19. package/dist/emulator/testing.d.ts +3 -6
  20. package/dist/emulator/testing.js +7 -20
  21. package/dist/emulator/testing.js.map +1 -1
  22. package/dist/emulator.cjs +59 -249
  23. package/dist/emulator.cjs.map +1 -1
  24. package/dist/emulator.d.cts +3 -5
  25. package/dist/emulator.d.ts +3 -5
  26. package/dist/emulator.js +5 -11
  27. package/dist/index.cjs +1373 -4859
  28. package/dist/index.cjs.map +1 -1
  29. package/dist/index.d.cts +81 -260
  30. package/dist/index.d.ts +81 -260
  31. package/dist/index.js +8 -158
  32. package/dist/{inline-DpcT8tV9.d.ts → templates-CGmRIQBV.d.cts} +89 -35
  33. package/dist/{inline-C7DkvuET.d.cts → templates-Cq94qOcf.d.ts} +89 -35
  34. package/dist/{types-D3JxUCfL.d.cts → types-DBStn0Dk.d.cts} +2 -26
  35. package/dist/{types-D3JxUCfL.d.ts → types-DBStn0Dk.d.ts} +2 -26
  36. package/dist/use-grist-BK0eke9K.d.cts +358 -0
  37. package/dist/use-grist-C5DMZ3wU.d.ts +358 -0
  38. package/package.json +8 -37
  39. package/dist/advanced.cjs +0 -2799
  40. package/dist/advanced.cjs.map +0 -1
  41. package/dist/advanced.d.cts +0 -822
  42. package/dist/advanced.d.ts +0 -822
  43. package/dist/advanced.js +0 -872
  44. package/dist/advanced.js.map +0 -1
  45. package/dist/chunk-22U7WAY3.js +0 -463
  46. package/dist/chunk-22U7WAY3.js.map +0 -1
  47. package/dist/chunk-64SVRWW7.js +0 -758
  48. package/dist/chunk-64SVRWW7.js.map +0 -1
  49. package/dist/chunk-6Y2IONGJ.js.map +0 -1
  50. package/dist/chunk-B4AKVSWV.js +0 -2362
  51. package/dist/chunk-B4AKVSWV.js.map +0 -1
  52. package/dist/chunk-ELGVOHFD.js +0 -32
  53. package/dist/chunk-ELGVOHFD.js.map +0 -1
  54. package/dist/chunk-GTTD5HPO.js +0 -36
  55. package/dist/chunk-GTTD5HPO.js.map +0 -1
  56. package/dist/chunk-NO6FYX33.js +0 -2084
  57. package/dist/chunk-NO6FYX33.js.map +0 -1
  58. package/dist/chunk-OPTOYD2Y.js +0 -277
  59. package/dist/chunk-OPTOYD2Y.js.map +0 -1
  60. package/dist/chunk-PM3B4WYV.js +0 -12
  61. package/dist/chunk-PM3B4WYV.js.map +0 -1
  62. package/dist/chunk-REV5AUXD.js +0 -25
  63. package/dist/chunk-REV5AUXD.js.map +0 -1
  64. package/dist/chunk-YQNMIL33.js.map +0 -1
  65. package/dist/create-grist-emulator-BCqp1BF9.d.ts +0 -81
  66. package/dist/create-grist-emulator-DBM8OSIR.d.cts +0 -81
  67. package/dist/emulator/devtools.cjs +0 -1404
  68. package/dist/emulator/devtools.cjs.map +0 -1
  69. package/dist/emulator/devtools.d.cts +0 -48
  70. package/dist/emulator/devtools.d.ts +0 -48
  71. package/dist/emulator/devtools.js +0 -506
  72. package/dist/emulator/devtools.js.map +0 -1
  73. package/dist/emulator/transports.cjs +0 -1132
  74. package/dist/emulator/transports.cjs.map +0 -1
  75. package/dist/emulator/transports.d.cts +0 -38
  76. package/dist/emulator/transports.d.ts +0 -38
  77. package/dist/emulator/transports.js +0 -12
  78. package/dist/emulator/transports.js.map +0 -1
  79. package/dist/templates-8k2LKoO-.d.cts +0 -24
  80. package/dist/templates-DIwi_mvI.d.ts +0 -24
  81. package/dist/use-grist-reads-DUMvJgV7.d.ts +0 -480
  82. package/dist/use-grist-reads-DpjVuI0H.d.cts +0 -480
  83. package/dist/use-grist-types-DdOLEJXN.d.cts +0 -499
  84. package/dist/use-grist-types-kwxoFeub.d.ts +0 -499
  85. package/dist/use-grist-widget-options-from-context-CGOlQ5Vc.d.cts +0 -43
  86. package/dist/use-grist-widget-options-from-context-CGOlQ5Vc.d.ts +0 -43
@@ -1,13 +1,10 @@
1
1
  import { ReactElement, ComponentType } from 'react';
2
2
  import { RenderOptions, RenderResult } from '@testing-library/react';
3
3
  export { RenderOptions, RenderResult, act, cleanup, fireEvent, render, screen, waitFor, within } from '@testing-library/react';
4
- import { CreateGristEmulatorOptions, GristEmulator } from '../emulator.cjs';
5
- import { GristReplicaDocument } from '../emulator.cjs';
4
+ import { CreateGristEmulatorOptions, GristEmulator, EmulatorEvent } from '../emulator.cjs';
5
+ export { p as presets } from '../templates-CGmRIQBV.cjs';
6
6
  import { UseGristOptions } from '../index.cjs';
7
- import { EmulatorEvent } from '../emulator.cjs';
8
- export { p as presets } from '../templates-8k2LKoO-.cjs';
9
- import '../use-grist-reads-DpjVuI0H.cjs';
10
- import 'zod';
7
+ import { GristReplicaDocument } from '../emulator.cjs';
11
8
 
12
9
  /**
13
10
  * `renderWithGrist(ui, options)` — drop-in replacement for
@@ -1,13 +1,10 @@
1
1
  import { ReactElement, ComponentType } from 'react';
2
2
  import { RenderOptions, RenderResult } from '@testing-library/react';
3
3
  export { RenderOptions, RenderResult, act, cleanup, fireEvent, render, screen, waitFor, within } from '@testing-library/react';
4
- import { CreateGristEmulatorOptions, GristEmulator } from '../emulator.js';
5
- import { GristReplicaDocument } from '../emulator.js';
4
+ import { CreateGristEmulatorOptions, GristEmulator, EmulatorEvent } from '../emulator.js';
5
+ export { p as presets } from '../templates-Cq94qOcf.js';
6
6
  import { UseGristOptions } from '../index.js';
7
- import { EmulatorEvent } from '../emulator.js';
8
- export { p as presets } from '../templates-DIwi_mvI.js';
9
- import '../use-grist-reads-DUMvJgV7.js';
10
- import 'zod';
7
+ import { GristReplicaDocument } from '../emulator.js';
11
8
 
12
9
  /**
13
10
  * `renderWithGrist(ui, options)` — drop-in replacement for
@@ -1,40 +1,27 @@
1
1
  import {
2
2
  GristWidgetApp
3
- } from "../chunk-6Y2IONGJ.js";
4
- import "../chunk-B4AKVSWV.js";
3
+ } from "../chunk-P6VTEKWO.js";
5
4
  import {
6
- resetEmbeddedHandshakeManagerForTests,
7
- resetGristEventsForTests,
8
- resetGristReadyCacheForTests
9
- } from "../chunk-NO6FYX33.js";
5
+ resetGristForTests
6
+ } from "../chunk-O7NY46QY.js";
10
7
  import {
11
8
  createGristEmulator,
12
9
  presets
13
- } from "../chunk-22U7WAY3.js";
14
- import "../chunk-YQNMIL33.js";
15
- import "../chunk-GTTD5HPO.js";
16
- import "../chunk-REV5AUXD.js";
17
- import "../chunk-OPTOYD2Y.js";
18
- import "../chunk-ELGVOHFD.js";
19
- import "../chunk-PM3B4WYV.js";
20
- import "../chunk-64SVRWW7.js";
10
+ } from "../chunk-B5ZZNLVS.js";
11
+ import "../chunk-4ZGVKGDF.js";
21
12
 
22
13
  // src/emulator/testing/render-with-grist.tsx
23
14
  import { render } from "@testing-library/react";
24
15
  function renderWithGrist(ui, options = {}) {
25
16
  fakeIframeContext();
26
- resetGristEventsForTests();
27
- resetGristReadyCacheForTests();
28
- resetEmbeddedHandshakeManagerForTests();
17
+ resetGristForTests();
29
18
  const emulator = options.existing ?? createGristEmulator(options.emulator ?? { document: presets.blank() });
30
19
  const result = render(ui, options);
31
20
  const originalUnmount = result.unmount;
32
21
  result.unmount = () => {
33
22
  originalUnmount();
34
23
  if (!options.existing) emulator.dispose();
35
- resetGristReadyCacheForTests();
36
- resetGristEventsForTests();
37
- resetEmbeddedHandshakeManagerForTests();
24
+ resetGristForTests();
38
25
  };
39
26
  return Object.assign(result, { emulator });
40
27
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/emulator/testing/render-with-grist.tsx","../../src/emulator/testing/smoke-test-widget.tsx","../../src/emulator/testing/jsdom-stubs.ts","../../src/emulator/testing/matchers.ts","../../src/emulator/testing/index.ts"],"sourcesContent":["/**\n * `renderWithGrist(ui, options)` — drop-in replacement for\n * `@testing-library/react`'s `render(ui)` that boots a Grist emulator\n * (inline transport) and exposes it to the test.\n *\n * The emulator is mounted as `window.grist`. The SDK hooks (`useGrist`,\n * `useGristReady`, etc.) detect it via the same path they use in production.\n *\n * Cleanup is automatic when the test's container unmounts.\n */\n\nimport type { ReactElement } from \"react\"\n\nimport { render, type RenderOptions, type RenderResult } from \"@testing-library/react\"\n\nimport { resetEmbeddedHandshakeManagerForTests } from \"../../sdk/internal/embedded-handshake-manager\"\nimport { resetGristReadyCacheForTests } from \"../../sdk/internal/ready-singleton\"\nimport { resetGristEventsForTests } from \"../../sdk/internal/subscriptions\"\nimport { createGristEmulator } from \"../create-grist-emulator\"\nimport type { CreateGristEmulatorOptions, GristEmulator } from \"../create-grist-emulator\"\nimport { presets } from \"../templates\"\n\nexport type RenderWithGristOptions = RenderOptions & {\n emulator?: CreateGristEmulatorOptions\n /** Skip mounting the emulator; you bring your own. Useful for shared fixtures. */\n existing?: GristEmulator\n}\n\nexport type RenderWithGristResult = RenderResult & {\n emulator: GristEmulator\n}\n\nexport function renderWithGrist(\n ui: ReactElement,\n options: RenderWithGristOptions = {}\n): RenderWithGristResult {\n // Make `window.self !== window.top` evaluate to `true` so\n // `useGristAvailability` reports the widget as embedded. We only do this in\n // jsdom; real browsers already enforce this distinction for iframes.\n fakeIframeContext()\n\n resetGristEventsForTests()\n resetGristReadyCacheForTests()\n resetEmbeddedHandshakeManagerForTests()\n\n const emulator =\n options.existing ??\n createGristEmulator(options.emulator ?? { document: presets.blank() })\n\n const result = render(ui, options)\n const originalUnmount = result.unmount\n result.unmount = () => {\n originalUnmount()\n if (!options.existing) emulator.dispose()\n resetGristReadyCacheForTests()\n resetGristEventsForTests()\n resetEmbeddedHandshakeManagerForTests()\n }\n return Object.assign(result, { emulator })\n}\n\nfunction fakeIframeContext() {\n if (typeof window === \"undefined\") return\n if (window.self !== window.top) return // already iframe-like\n try {\n Object.defineProperty(window, \"top\", {\n configurable: true,\n get: () => ({}),\n })\n } catch {\n // Some jsdom versions block redefinition; fail silently — tests using\n // `useGristAvailability` will need to bypass it explicitly in that case.\n }\n}\n","import { Component, type ComponentType, type ReactNode } from \"react\"\nimport { waitFor } from \"@testing-library/react\"\n\nimport { GristWidgetApp } from \"../../app/mount-grist-widget\"\nimport type { GristColumnToMap, GristReplicaDocument, GristWidgetColumnMap, UseGristOptions } from \"../../sdk\"\nimport { installJsdomStubs } from \"./jsdom-stubs\"\nimport { renderWithGrist } from \"./render-with-grist\"\n\nexport type SmokeTestWidgetOptions = {\n /** The widget, as `src/main.tsx` passes it to `mountGristWidget()`. */\n App: ComponentType\n /** The widget's `GRIST_OPTIONS`. */\n options: UseGristOptions\n /**\n * The Grist document to run against. Defaults to one table with a column\n * per column the widget declares, typed after the first type it accepts,\n * and one row of sample values.\n */\n document?: GristReplicaDocument\n /** How long to wait for the widget to render, in ms. Defaults to 10 000. */\n timeout?: number\n}\n\n/**\n * Mounts the widget the way `mountGristWidget()` does inside Grist, against\n * an emulated Grist document with every column it declares mapped and the\n * access it requests granted, and waits until it renders past\n * `GristBoundary`. Resolves once it has,\n * rejects with the widget's own error if it throws, or with what the page\n * showed instead if it never gets there. Then unmounts.\n *\n * A widget's whole smoke test:\n *\n * ```tsx\n * import { it } from \"vitest\"\n * import { smokeTestWidget } from \"grist-widget-sdk/emulator/testing\"\n * import App, { GRIST_OPTIONS } from \"./App\"\n *\n * it(\"renders inside Grist\", () => smokeTestWidget({ App, options: GRIST_OPTIONS }))\n * ```\n *\n * It only reads `GRIST_OPTIONS`, so it stays valid as the widget changes,\n * and it improves with each SDK release.\n */\nexport async function smokeTestWidget({\n App,\n options,\n document,\n timeout = 10_000,\n}: SmokeTestWidgetOptions): Promise<void> {\n installJsdomStubs()\n\n const crash: { error: unknown } = { error: undefined }\n function Probe() {\n return (\n <CatchRenderError crash={crash}>\n <div data-grist-smoke-test=\"\">\n <App />\n </div>\n </CatchRenderError>\n )\n }\n\n const columns = declaredColumns(options)\n const gristDocument = document ?? smokeTestDocument(columns)\n const result = renderWithGrist(<GristWidgetApp App={Probe} options={options} />, {\n emulator: {\n document: gristDocument,\n // Granted as the user would in Grist's widget panel: the access the\n // widget asks for, unless the given document says otherwise.\n widgetInteraction: gristDocument.widgetInteraction ?? { access_level: options.requiredAccess ?? \"read table\" },\n },\n })\n try {\n if (columns.length > 0) result.emulator.setColumnMappings(mapEveryColumn(columns))\n\n try {\n await waitFor(\n () => {\n if (crash.error !== undefined) return\n const probe = result.container.querySelector(\"[data-grist-smoke-test]\")\n if (!probe || !probe.hasChildNodes()) throw new Error(\"not rendered yet\")\n },\n { timeout }\n )\n } catch {\n const shown = result.container.textContent?.trim().replace(/\\s+/g, \" \") || \"nothing\"\n throw new Error(\n `smokeTestWidget: the widget didn't render past GristBoundary within ${timeout} ms. ` +\n `The page showed: \"${shown.slice(0, 300)}\".`\n )\n }\n if (crash.error !== undefined) throw crash.error\n } finally {\n result.unmount()\n }\n}\n\n/** Records the widget's render error, so the test fails with it rather than a timeout. */\nclass CatchRenderError extends Component<{ crash: { error: unknown }; children: ReactNode }, { failed: boolean }> {\n state = { failed: false }\n\n static getDerivedStateFromError() {\n return { failed: true }\n }\n\n componentDidCatch(error: unknown) {\n this.props.crash.error = error\n }\n\n render() {\n return this.state.failed ? null : this.props.children\n }\n}\n\ntype Column = { name: string; type: string; allowMultiple: boolean }\n\nfunction declaredColumns(options: UseGristOptions): Column[] {\n return (options.columns ?? []).map((column: string | GristColumnToMap) =>\n typeof column === \"string\"\n ? { name: column, type: \"Any\", allowMultiple: false }\n : { name: column.name, type: firstType(column.type), allowMultiple: column.allowMultiple === true }\n )\n}\n\n/** The first Grist type a column accepts: `\"Text,Ref,Choice\"` gives `\"Text\"`. */\nfunction firstType(type: string | undefined): string {\n return (type ?? \"Any\").split(\",\")[0].trim() || \"Any\"\n}\n\nfunction mapEveryColumn(columns: Column[]): GristWidgetColumnMap {\n return Object.fromEntries(columns.map((c) => [c.name, c.allowMultiple ? [c.name] : c.name]))\n}\n\n/** One table with a column per declared column, and one row of sample values. */\nfunction smokeTestDocument(columns: Column[]): GristReplicaDocument {\n return {\n generatedAt: \"1970-01-01T00:00:00.000Z\",\n docName: \"Smoke test\",\n mode: \"schema+data\",\n tables: {\n Table1: {\n columns: Object.fromEntries(columns.map((c) => [c.name, { type: c.type }])),\n rows: [{ id: 1, ...Object.fromEntries(columns.map((c) => [c.name, sampleValue(c.type)])) }],\n },\n },\n }\n}\n\nfunction sampleValue(type: string): unknown {\n switch (type) {\n case \"Numeric\":\n case \"Int\":\n return 42\n case \"Bool\":\n return true\n case \"Date\":\n case \"DateTime\":\n return 1767225600 // 2026-01-01, as Grist stores it: epoch seconds\n default:\n return \"Sample\"\n }\n}\n","/**\n * Browser APIs jsdom lacks but widget UI commonly uses, as inert stand-ins:\n * `ResizeObserver` (charts, virtualized lists, popovers) and\n * `window.matchMedia` (the theme, responsive hooks; it reports a light,\n * wide screen). Only installs what's missing, so a test's own mock wins.\n *\n * {@link smokeTestWidget} calls it. For a widget's other tests, call it at\n * the top of the test file or in a Vitest setup file.\n */\nexport function installJsdomStubs(): void {\n if (typeof window === \"undefined\") return\n\n globalThis.ResizeObserver ??= class ResizeObserver {\n observe() {}\n unobserve() {}\n disconnect() {}\n }\n\n window.matchMedia ??= (query: string) =>\n ({\n matches: false,\n media: query,\n onchange: null,\n addEventListener() {},\n removeEventListener() {},\n addListener() {},\n removeListener() {},\n dispatchEvent: () => false,\n }) as MediaQueryList\n}\n","/**\n * Vitest helpers to assert what the widget did to the document.\n *\n * Usage:\n *\n * const { emulator } = renderWithGrist(<MyWidget />)\n * await userEvent.click(screen.getByText(\"Add\"))\n * expect(actionsOf(emulator)).toContainEqual([\"AddRecord\", \"Tasks\", null, { title: \"X\" }])\n */\n\nimport type { GristEmulator } from \"../create-grist-emulator\"\nimport type { EmulatorEvent } from \"../core/event-bus\"\n\nexport function actionsOf(emulator: GristEmulator): unknown[][] {\n return emulator.bus\n .history()\n .filter((e: EmulatorEvent): e is Extract<EmulatorEvent, { type: \"user-action\" }> => e.type === \"user-action\")\n .map((e) => e.action as unknown[])\n}\n\nexport function eventsOf(emulator: GristEmulator): readonly EmulatorEvent[] {\n return emulator.bus.history()\n}\n\n/** Bus event kinds aligned with grist host callbacks (see inline transport). */\nexport type WaitForEventKind = \"ready\" | \"record\" | \"records\" | \"options\" | \"theme\" | \"cursor\"\n\nexport type WaitForEventOptions = {\n timeoutMs?: number\n}\n\nfunction predicateForKind(kind: WaitForEventKind): (event: EmulatorEvent) => boolean {\n switch (kind) {\n case \"ready\":\n return (e) => e.type === \"ready\"\n case \"record\":\n case \"cursor\":\n return (e) => e.type === \"cursor-change\"\n case \"records\":\n return (e) => e.type === \"data-change\"\n case \"options\":\n return (e) => e.type === \"options-change\"\n case \"theme\":\n return (e) => e.type === \"theme-change\"\n }\n}\n\nfunction waitForNextEvent(\n emulator: GristEmulator,\n predicate: (event: EmulatorEvent) => boolean,\n timeoutMs: number,\n label?: string\n): Promise<EmulatorEvent> {\n return new Promise((resolve, reject) => {\n const timer = setTimeout(() => {\n off()\n const hint = label ? ` (kind: ${label})` : \"\"\n const recent = emulator.bus\n .history()\n .slice(-5)\n .map((e) => e.type)\n .join(\", \")\n reject(\n new Error(\n `waitForEvent timed out after ${timeoutMs}ms${hint}.` +\n (recent ? ` Recent bus events: ${recent}.` : \" No bus events yet.\")\n )\n )\n }, timeoutMs)\n const off = emulator.bus.on((event) => {\n if (predicate(event)) {\n clearTimeout(timer)\n off()\n resolve(event)\n }\n })\n })\n}\n\nexport function waitForEvent(\n emulator: GristEmulator,\n kind: WaitForEventKind,\n options?: WaitForEventOptions\n): Promise<EmulatorEvent>\nexport function waitForEvent(\n emulator: GristEmulator,\n predicate: (event: EmulatorEvent) => boolean,\n timeoutMs?: number\n): Promise<EmulatorEvent>\nexport function waitForEvent(\n emulator: GristEmulator,\n kindOrPredicate: WaitForEventKind | ((event: EmulatorEvent) => boolean),\n timeoutOrOptions: number | WaitForEventOptions = 1000\n): Promise<EmulatorEvent> {\n if (typeof kindOrPredicate === \"string\") {\n const opts = typeof timeoutOrOptions === \"object\" ? timeoutOrOptions : {}\n const timeoutMs = opts.timeoutMs ?? 1000\n return waitForNextEvent(emulator, predicateForKind(kindOrPredicate), timeoutMs, kindOrPredicate)\n }\n const timeoutMs = typeof timeoutOrOptions === \"number\" ? timeoutOrOptions : 1000\n return waitForNextEvent(emulator, kindOrPredicate, timeoutMs)\n}\n","/**\n * Test entry point — `grist-widget-sdk/emulator/testing`.\n *\n * Bundles `renderWithGrist`, matchers, document presets, and the most-used\n * @testing-library/react primitives so a typical test file only needs one\n * import line:\n *\n * import {\n * renderWithGrist, presets, screen, fireEvent, waitFor, actionsOf,\n * } from \"grist-widget-sdk/emulator/testing\"\n */\n\nexport { renderWithGrist } from \"./render-with-grist\"\nexport type {\n RenderWithGristOptions,\n RenderWithGristResult,\n} from \"./render-with-grist\"\n\nexport { smokeTestWidget, type SmokeTestWidgetOptions } from \"./smoke-test-widget\"\nexport { installJsdomStubs } from \"./jsdom-stubs\"\n\nexport {\n actionsOf,\n eventsOf,\n waitForEvent,\n type WaitForEventKind,\n type WaitForEventOptions,\n} from \"./matchers\"\n\n// Document fixtures (re-export from the emulator entry for convenience).\nexport { presets } from \"../templates\"\n\n// @testing-library/react convenience re-exports — see AGENTS.md\nexport {\n act,\n cleanup,\n fireEvent,\n render,\n screen,\n waitFor,\n within,\n} from \"@testing-library/react\"\nexport type { RenderOptions, RenderResult } from \"@testing-library/react\"\n"],"mappings":"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","names":["timeoutMs","render","waitFor"]}
1
+ {"version":3,"sources":["../../src/emulator/testing/render-with-grist.tsx","../../src/emulator/testing/smoke-test-widget.tsx","../../src/emulator/testing/jsdom-stubs.ts","../../src/emulator/testing/matchers.ts","../../src/emulator/testing/index.ts"],"sourcesContent":["/**\n * `renderWithGrist(ui, options)` — drop-in replacement for\n * `@testing-library/react`'s `render(ui)` that boots a Grist emulator\n * (inline transport) and exposes it to the test.\n *\n * The emulator is mounted as `window.grist`. The SDK hooks (`useGrist`,\n * `useGristReady`, etc.) detect it via the same path they use in production.\n *\n * Cleanup is automatic when the test's container unmounts.\n */\n\nimport type { ReactElement } from \"react\"\n\nimport { render, type RenderOptions, type RenderResult } from \"@testing-library/react\"\n\nimport { resetGristForTests } from \"../../sdk/use-grist\"\nimport { createGristEmulator } from \"../create-grist-emulator\"\nimport type { CreateGristEmulatorOptions, GristEmulator } from \"../create-grist-emulator\"\nimport { presets } from \"../templates\"\n\nexport type RenderWithGristOptions = RenderOptions & {\n emulator?: CreateGristEmulatorOptions\n /** Skip mounting the emulator; you bring your own. Useful for shared fixtures. */\n existing?: GristEmulator\n}\n\nexport type RenderWithGristResult = RenderResult & {\n emulator: GristEmulator\n}\n\nexport function renderWithGrist(\n ui: ReactElement,\n options: RenderWithGristOptions = {}\n): RenderWithGristResult {\n // Make `window.self !== window.top` evaluate to `true` so\n // `useGristAvailability` reports the widget as embedded. We only do this in\n // jsdom; real browsers already enforce this distinction for iframes.\n fakeIframeContext()\n\n resetGristForTests()\n\n const emulator =\n options.existing ??\n createGristEmulator(options.emulator ?? { document: presets.blank() })\n\n const result = render(ui, options)\n const originalUnmount = result.unmount\n result.unmount = () => {\n originalUnmount()\n if (!options.existing) emulator.dispose()\n resetGristForTests()\n }\n return Object.assign(result, { emulator })\n}\n\nfunction fakeIframeContext() {\n if (typeof window === \"undefined\") return\n if (window.self !== window.top) return // already iframe-like\n try {\n Object.defineProperty(window, \"top\", {\n configurable: true,\n get: () => ({}),\n })\n } catch {\n // Some jsdom versions block redefinition; fail silently — tests using\n // `useGristAvailability` will need to bypass it explicitly in that case.\n }\n}\n","import { Component, type ComponentType, type ReactNode } from \"react\"\nimport { waitFor } from \"@testing-library/react\"\n\nimport { GristWidgetApp } from \"../../app/mount-grist-widget\"\nimport type { GristColumnToMap, GristReplicaDocument, GristWidgetColumnMap, UseGristOptions } from \"../../sdk\"\nimport { installJsdomStubs } from \"./jsdom-stubs\"\nimport { renderWithGrist } from \"./render-with-grist\"\n\nexport type SmokeTestWidgetOptions = {\n /** The widget, as `src/main.tsx` passes it to `mountGristWidget()`. */\n App: ComponentType\n /** The widget's `GRIST_OPTIONS`. */\n options: UseGristOptions\n /**\n * The Grist document to run against. Defaults to one table with a column\n * per column the widget declares, typed after the first type it accepts,\n * and one row of sample values.\n */\n document?: GristReplicaDocument\n /** How long to wait for the widget to render, in ms. Defaults to 10 000. */\n timeout?: number\n}\n\n/**\n * Mounts the widget the way `mountGristWidget()` does inside Grist, against\n * an emulated Grist document with every column it declares mapped and the\n * access it requests granted, and waits until it renders past\n * `GristBoundary`. Resolves once it has,\n * rejects with the widget's own error if it throws, or with what the page\n * showed instead if it never gets there. Then unmounts.\n *\n * A widget's whole smoke test:\n *\n * ```tsx\n * import { it } from \"vitest\"\n * import { smokeTestWidget } from \"grist-widget-sdk/emulator/testing\"\n * import App, { GRIST_OPTIONS } from \"./App\"\n *\n * it(\"renders inside Grist\", () => smokeTestWidget({ App, options: GRIST_OPTIONS }))\n * ```\n *\n * It only reads `GRIST_OPTIONS`, so it stays valid as the widget changes,\n * and it improves with each SDK release.\n */\nexport async function smokeTestWidget({\n App,\n options,\n document,\n timeout = 10_000,\n}: SmokeTestWidgetOptions): Promise<void> {\n installJsdomStubs()\n\n const crash: { error: unknown } = { error: undefined }\n function Probe() {\n return (\n <CatchRenderError crash={crash}>\n <div data-grist-smoke-test=\"\">\n <App />\n </div>\n </CatchRenderError>\n )\n }\n\n const columns = declaredColumns(options)\n const gristDocument = document ?? smokeTestDocument(columns)\n const result = renderWithGrist(<GristWidgetApp App={Probe} options={options} />, {\n emulator: {\n document: gristDocument,\n // Granted as the user would in Grist's widget panel: the access the\n // widget asks for, unless the given document says otherwise.\n widgetInteraction: gristDocument.widgetInteraction ?? { access_level: options.requiredAccess ?? \"read table\" },\n },\n })\n try {\n if (columns.length > 0) result.emulator.setColumnMappings(mapEveryColumn(columns))\n\n try {\n await waitFor(\n () => {\n if (crash.error !== undefined) return\n const probe = result.container.querySelector(\"[data-grist-smoke-test]\")\n if (!probe || !probe.hasChildNodes()) throw new Error(\"not rendered yet\")\n },\n { timeout }\n )\n } catch {\n const shown = result.container.textContent?.trim().replace(/\\s+/g, \" \") || \"nothing\"\n throw new Error(\n `smokeTestWidget: the widget didn't render past GristBoundary within ${timeout} ms. ` +\n `The page showed: \"${shown.slice(0, 300)}\".`\n )\n }\n if (crash.error !== undefined) throw crash.error\n } finally {\n result.unmount()\n }\n}\n\n/** Records the widget's render error, so the test fails with it rather than a timeout. */\nclass CatchRenderError extends Component<{ crash: { error: unknown }; children: ReactNode }, { failed: boolean }> {\n state = { failed: false }\n\n static getDerivedStateFromError() {\n return { failed: true }\n }\n\n componentDidCatch(error: unknown) {\n this.props.crash.error = error\n }\n\n render() {\n return this.state.failed ? null : this.props.children\n }\n}\n\ntype Column = { name: string; type: string; allowMultiple: boolean }\n\nfunction declaredColumns(options: UseGristOptions): Column[] {\n return (options.columns ?? []).map((column: string | GristColumnToMap) =>\n typeof column === \"string\"\n ? { name: column, type: \"Any\", allowMultiple: false }\n : { name: column.name, type: firstType(column.type), allowMultiple: column.allowMultiple === true }\n )\n}\n\n/** The first Grist type a column accepts: `\"Text,Ref,Choice\"` gives `\"Text\"`. */\nfunction firstType(type: string | undefined): string {\n return (type ?? \"Any\").split(\",\")[0].trim() || \"Any\"\n}\n\nfunction mapEveryColumn(columns: Column[]): GristWidgetColumnMap {\n return Object.fromEntries(columns.map((c) => [c.name, c.allowMultiple ? [c.name] : c.name]))\n}\n\n/** One table with a column per declared column, and one row of sample values. */\nfunction smokeTestDocument(columns: Column[]): GristReplicaDocument {\n return {\n generatedAt: \"1970-01-01T00:00:00.000Z\",\n docName: \"Smoke test\",\n mode: \"schema+data\",\n tables: {\n Table1: {\n columns: Object.fromEntries(columns.map((c) => [c.name, { type: c.type }])),\n rows: [{ id: 1, ...Object.fromEntries(columns.map((c) => [c.name, sampleValue(c.type)])) }],\n },\n },\n }\n}\n\nfunction sampleValue(type: string): unknown {\n switch (type) {\n case \"Numeric\":\n case \"Int\":\n return 42\n case \"Bool\":\n return true\n case \"Date\":\n case \"DateTime\":\n return 1767225600 // 2026-01-01, as Grist stores it: epoch seconds\n default:\n return \"Sample\"\n }\n}\n","/**\n * Browser APIs jsdom lacks but widget UI commonly uses, as inert stand-ins:\n * `ResizeObserver` (charts, virtualized lists, popovers) and\n * `window.matchMedia` (the theme, responsive hooks; it reports a light,\n * wide screen). Only installs what's missing, so a test's own mock wins.\n *\n * {@link smokeTestWidget} calls it. For a widget's other tests, call it at\n * the top of the test file or in a Vitest setup file.\n */\nexport function installJsdomStubs(): void {\n if (typeof window === \"undefined\") return\n\n globalThis.ResizeObserver ??= class ResizeObserver {\n observe() {}\n unobserve() {}\n disconnect() {}\n }\n\n window.matchMedia ??= (query: string) =>\n ({\n matches: false,\n media: query,\n onchange: null,\n addEventListener() {},\n removeEventListener() {},\n addListener() {},\n removeListener() {},\n dispatchEvent: () => false,\n }) as MediaQueryList\n}\n","/**\n * Vitest helpers to assert what the widget did to the document.\n *\n * Usage:\n *\n * const { emulator } = renderWithGrist(<MyWidget />)\n * await userEvent.click(screen.getByText(\"Add\"))\n * expect(actionsOf(emulator)).toContainEqual([\"AddRecord\", \"Tasks\", null, { title: \"X\" }])\n */\n\nimport type { GristEmulator } from \"../create-grist-emulator\"\nimport type { EmulatorEvent } from \"../core/event-bus\"\n\nexport function actionsOf(emulator: GristEmulator): unknown[][] {\n return emulator.bus\n .history()\n .filter((e: EmulatorEvent): e is Extract<EmulatorEvent, { type: \"user-action\" }> => e.type === \"user-action\")\n .map((e) => e.action as unknown[])\n}\n\nexport function eventsOf(emulator: GristEmulator): readonly EmulatorEvent[] {\n return emulator.bus.history()\n}\n\n/** Bus event kinds aligned with grist host callbacks (see inline transport). */\nexport type WaitForEventKind = \"ready\" | \"record\" | \"records\" | \"options\" | \"theme\" | \"cursor\"\n\nexport type WaitForEventOptions = {\n timeoutMs?: number\n}\n\nfunction predicateForKind(kind: WaitForEventKind): (event: EmulatorEvent) => boolean {\n switch (kind) {\n case \"ready\":\n return (e) => e.type === \"ready\"\n case \"record\":\n case \"cursor\":\n return (e) => e.type === \"cursor-change\"\n case \"records\":\n return (e) => e.type === \"data-change\"\n case \"options\":\n return (e) => e.type === \"options-change\"\n case \"theme\":\n return (e) => e.type === \"theme-change\"\n }\n}\n\nfunction waitForNextEvent(\n emulator: GristEmulator,\n predicate: (event: EmulatorEvent) => boolean,\n timeoutMs: number,\n label?: string\n): Promise<EmulatorEvent> {\n return new Promise((resolve, reject) => {\n const timer = setTimeout(() => {\n off()\n const hint = label ? ` (kind: ${label})` : \"\"\n const recent = emulator.bus\n .history()\n .slice(-5)\n .map((e) => e.type)\n .join(\", \")\n reject(\n new Error(\n `waitForEvent timed out after ${timeoutMs}ms${hint}.` +\n (recent ? ` Recent bus events: ${recent}.` : \" No bus events yet.\")\n )\n )\n }, timeoutMs)\n const off = emulator.bus.on((event) => {\n if (predicate(event)) {\n clearTimeout(timer)\n off()\n resolve(event)\n }\n })\n })\n}\n\nexport function waitForEvent(\n emulator: GristEmulator,\n kind: WaitForEventKind,\n options?: WaitForEventOptions\n): Promise<EmulatorEvent>\nexport function waitForEvent(\n emulator: GristEmulator,\n predicate: (event: EmulatorEvent) => boolean,\n timeoutMs?: number\n): Promise<EmulatorEvent>\nexport function waitForEvent(\n emulator: GristEmulator,\n kindOrPredicate: WaitForEventKind | ((event: EmulatorEvent) => boolean),\n timeoutOrOptions: number | WaitForEventOptions = 1000\n): Promise<EmulatorEvent> {\n if (typeof kindOrPredicate === \"string\") {\n const opts = typeof timeoutOrOptions === \"object\" ? timeoutOrOptions : {}\n const timeoutMs = opts.timeoutMs ?? 1000\n return waitForNextEvent(emulator, predicateForKind(kindOrPredicate), timeoutMs, kindOrPredicate)\n }\n const timeoutMs = typeof timeoutOrOptions === \"number\" ? timeoutOrOptions : 1000\n return waitForNextEvent(emulator, kindOrPredicate, timeoutMs)\n}\n","/**\n * Test entry point — `grist-widget-sdk/emulator/testing`.\n *\n * Bundles `renderWithGrist`, matchers, document presets, and the most-used\n * @testing-library/react primitives so a typical test file only needs one\n * import line:\n *\n * import {\n * renderWithGrist, presets, screen, fireEvent, waitFor, actionsOf,\n * } from \"grist-widget-sdk/emulator/testing\"\n */\n\nexport { renderWithGrist } from \"./render-with-grist\"\nexport type {\n RenderWithGristOptions,\n RenderWithGristResult,\n} from \"./render-with-grist\"\n\nexport { smokeTestWidget, type SmokeTestWidgetOptions } from \"./smoke-test-widget\"\nexport { installJsdomStubs } from \"./jsdom-stubs\"\n\nexport {\n actionsOf,\n eventsOf,\n waitForEvent,\n type WaitForEventKind,\n type WaitForEventOptions,\n} from \"./matchers\"\n\n// Document fixtures (re-export from the emulator entry for convenience).\nexport { presets } from \"../templates\"\n\n// @testing-library/react convenience re-exports — see AGENTS.md\nexport {\n act,\n cleanup,\n fireEvent,\n render,\n screen,\n waitFor,\n within,\n} from \"@testing-library/react\"\nexport type { RenderOptions, RenderResult } from \"@testing-library/react\"\n"],"mappings":"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","names":["timeoutMs","render","waitFor"]}
package/dist/emulator.cjs CHANGED
@@ -86,26 +86,7 @@ function coerceGristDocApiRefRowId(value) {
86
86
  return void 0;
87
87
  }
88
88
 
89
- // src/sdk/helpers/grist-safe-parse.ts
90
- var import_zod = require("zod");
91
- var gristSafeRefValueSchema = import_zod.z.object({
92
- rowId: import_zod.z.number(),
93
- display: import_zod.z.string().nullable().optional()
94
- });
95
- var gristSafeRefListValueSchema = import_zod.z.object({
96
- rowIds: import_zod.z.array(import_zod.z.number()),
97
- displays: import_zod.z.array(import_zod.z.string()).optional()
98
- });
99
- var gristCellValueSchema = import_zod.z.union([
100
- import_zod.z.null(),
101
- import_zod.z.string(),
102
- import_zod.z.number(),
103
- import_zod.z.boolean(),
104
- import_zod.z.date(),
105
- import_zod.z.array(import_zod.z.unknown()),
106
- gristSafeRefValueSchema,
107
- gristSafeRefListValueSchema
108
- ]);
89
+ // src/emulator/core/display-helpers.ts
109
90
  function inferDisplayHelpersByRef(columns) {
110
91
  const out = {};
111
92
  const helperColumns = Object.entries(columns).filter(
@@ -918,8 +899,9 @@ function createWidgetApiImpl(state, bus) {
918
899
  async getOptions() {
919
900
  return Object.keys(state.widgetOptions).length > 0 ? { ...state.widgetOptions } : null;
920
901
  },
902
+ // Replaces every option, as Grist's WidgetAPI.setOptions does.
921
903
  async setOptions(options) {
922
- state.widgetOptions = { ...state.widgetOptions, ...options };
904
+ state.widgetOptions = { ...options };
923
905
  emit();
924
906
  },
925
907
  async clearOptions() {
@@ -985,157 +967,6 @@ function buildGristThemeHostMessage(appearance, options) {
985
967
  };
986
968
  }
987
969
 
988
- // src/emulator/transports/iframe.ts
989
- var import_grain_rpc = require("grain-rpc");
990
- function createIframeTransport(options) {
991
- const { iframe, state, bus, api, targetOrigin = "*" } = options;
992
- const timers = /* @__PURE__ */ new Set();
993
- const rpc = new import_grain_rpc.Rpc({
994
- sendMessage: (msg) => {
995
- const target = iframe.contentWindow;
996
- if (!target) return;
997
- target.postMessage(msg, targetOrigin);
998
- },
999
- logger: { info: () => {
1000
- }, warn: () => {
1001
- } }
1002
- });
1003
- rpc.queueOutgoingUntilReadyMessage();
1004
- const receiveFromWidget = rpc.receiveMessage.bind(rpc);
1005
- rpc.receiveMessage = (msg) => {
1006
- if (msg && typeof msg === "object" && msg.mtype === import_grain_rpc.MsgType.RpcCall && msg.mdest === "grist") {
1007
- const copy = { ...msg };
1008
- delete copy.mdest;
1009
- receiveFromWidget(copy);
1010
- return;
1011
- }
1012
- receiveFromWidget(msg);
1013
- };
1014
- const onMessage = (event) => {
1015
- if (event.source !== iframe.contentWindow) return;
1016
- if (!isRpcEnvelope(event.data)) return;
1017
- rpc.receiveMessage(event.data);
1018
- };
1019
- window.addEventListener("message", onMessage);
1020
- rpc.registerImpl("GristView", api.GristView);
1021
- rpc.registerImpl("GristDocAPI", api.GristDocAPI);
1022
- rpc.registerImpl("WidgetAPI", api.WidgetAPI);
1023
- rpc.registerImpl("CustomSectionAPI", api.CustomSectionAPI);
1024
- rpc.registerImpl("_grist_api", api.GristAPI);
1025
- rpc.registerImpl("CommandAPI", api.CommandAPI);
1026
- const buildInteraction = options.buildInteraction ?? defaultInteraction(state);
1027
- const cursorOf = options.cursor ?? (() => state.selection.cursor);
1028
- const initialCursorOf = options.initialCursor ?? (() => state.selection.rowId);
1029
- function fireDataChange(tableId, opts) {
1030
- safePostMessage({
1031
- tableId: tableId ?? state.selection.tableId,
1032
- dataChange: true,
1033
- ...opts?.mappingsChange ? { mappingsChange: true } : {}
1034
- });
1035
- }
1036
- function fireCursor(rowId, opts) {
1037
- const id = rowId === void 0 ? initialCursorOf() : rowId;
1038
- if (id === null) return;
1039
- safePostMessage({
1040
- tableId: state.selection.tableId,
1041
- rowId: id,
1042
- cursor: cursorOf(),
1043
- ...opts?.mappingsChange ? { mappingsChange: true } : {}
1044
- });
1045
- }
1046
- function fireOptions() {
1047
- safePostMessage({
1048
- options: hasOptions(state.widgetOptions) ? { ...state.widgetOptions } : null,
1049
- settings: { ...buildInteraction() }
1050
- });
1051
- }
1052
- function fireTheme(message) {
1053
- safePostMessage(message);
1054
- }
1055
- function emitInitialState(opts) {
1056
- fireOptions();
1057
- if (options.initialTheme) {
1058
- fireTheme(buildGristThemeHostMessage(options.initialTheme, { fromReady: true }));
1059
- }
1060
- fireDataChange(void 0, opts);
1061
- fireCursor(void 0, opts);
1062
- }
1063
- function emitInitialStateWithReplay(opts) {
1064
- emitInitialState(opts);
1065
- queueMicrotask(() => emitInitialState(opts));
1066
- const timer = setTimeout(() => {
1067
- timers.delete(timer);
1068
- emitInitialState(opts);
1069
- }, 0);
1070
- timers.add(timer);
1071
- }
1072
- const offBus = bus.on((event) => {
1073
- switch (event.type) {
1074
- case "ready":
1075
- emitInitialStateWithReplay();
1076
- break;
1077
- case "data-change":
1078
- fireDataChange(event.tableId);
1079
- break;
1080
- case "cursor-change": {
1081
- const rowId = event.rowId === null ? state.selection.rowId : event.rowId;
1082
- if (rowId !== null) {
1083
- safePostMessage({
1084
- tableId: event.tableId,
1085
- rowId,
1086
- cursor: cursorOf()
1087
- });
1088
- }
1089
- fireDataChange(event.tableId);
1090
- break;
1091
- }
1092
- case "options-change":
1093
- fireOptions();
1094
- break;
1095
- case "mappings-change":
1096
- emitInitialStateWithReplay({ mappingsChange: true });
1097
- break;
1098
- case "theme-change":
1099
- fireTheme(event.message);
1100
- break;
1101
- }
1102
- });
1103
- rpc.processIncoming();
1104
- function safePostMessage(payload) {
1105
- try {
1106
- rpc.postMessage(payload);
1107
- } catch (err) {
1108
- console.warn("[grist-emulator/iframe] failed to deliver postMessage", err);
1109
- }
1110
- }
1111
- return {
1112
- dispose() {
1113
- window.removeEventListener("message", onMessage);
1114
- offBus();
1115
- for (const timer of timers) clearTimeout(timer);
1116
- timers.clear();
1117
- },
1118
- fireDataChange,
1119
- fireCursor,
1120
- fireOptions,
1121
- fireTheme,
1122
- emitInitialState
1123
- };
1124
- }
1125
- function hasOptions(options) {
1126
- return Object.keys(options).length > 0;
1127
- }
1128
- function isRpcEnvelope(data) {
1129
- if (!data || typeof data !== "object") return false;
1130
- return typeof data.mtype === "number";
1131
- }
1132
- function defaultInteraction(state) {
1133
- return () => ({
1134
- access_level: state.configuration?.requiredAccess ?? "read table",
1135
- ...state.widgetInteraction
1136
- });
1137
- }
1138
-
1139
970
  // src/sdk/helpers/grist-user-actions.ts
1140
971
  function gristUpdateRecordAction(tableId, rowId, patch) {
1141
972
  return ["UpdateRecord", tableId, rowId, patch];
@@ -1191,14 +1022,6 @@ function createInlineTransport(options) {
1191
1022
  logErr(err);
1192
1023
  }
1193
1024
  }
1194
- lastRecord = { record: null, mappings };
1195
- for (const [cb] of recordCbs) {
1196
- try {
1197
- cb(null, mappings);
1198
- } catch (err) {
1199
- logErr(err);
1200
- }
1201
- }
1202
1025
  return;
1203
1026
  }
1204
1027
  if (rowId === null) {
@@ -1537,57 +1360,57 @@ function installFakeIframeContext(target) {
1537
1360
  }
1538
1361
 
1539
1362
  // src/emulator/validators.ts
1540
- var import_zod2 = require("zod");
1541
- var emulatorRowSchema = import_zod2.z.object({ id: import_zod2.z.number() }).catchall(import_zod2.z.unknown());
1542
- var formulaKindSchema = import_zod2.z.enum(["computed", "trigger", "none", "empty_slot"]);
1543
- var replicaColumnSchema = import_zod2.z.object({
1544
- colRef: import_zod2.z.number().optional(),
1545
- type: import_zod2.z.string().optional(),
1546
- label: import_zod2.z.string().optional(),
1547
- parentPos: import_zod2.z.number().optional(),
1548
- isFormula: import_zod2.z.boolean().optional(),
1363
+ var import_zod = require("zod");
1364
+ var emulatorRowSchema = import_zod.z.object({ id: import_zod.z.number() }).catchall(import_zod.z.unknown());
1365
+ var formulaKindSchema = import_zod.z.enum(["computed", "trigger", "none", "empty_slot"]);
1366
+ var replicaColumnSchema = import_zod.z.object({
1367
+ colRef: import_zod.z.number().optional(),
1368
+ type: import_zod.z.string().optional(),
1369
+ label: import_zod.z.string().optional(),
1370
+ parentPos: import_zod.z.number().optional(),
1371
+ isFormula: import_zod.z.boolean().optional(),
1549
1372
  formulaKind: formulaKindSchema.optional(),
1550
- formula: import_zod2.z.string().optional(),
1551
- description: import_zod2.z.string().optional(),
1552
- widgetOptions: import_zod2.z.record(import_zod2.z.string(), import_zod2.z.unknown()).optional(),
1553
- displayColRef: import_zod2.z.number().optional(),
1554
- visibleColRef: import_zod2.z.number().optional()
1373
+ formula: import_zod.z.string().optional(),
1374
+ description: import_zod.z.string().optional(),
1375
+ widgetOptions: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional(),
1376
+ displayColRef: import_zod.z.number().optional(),
1377
+ visibleColRef: import_zod.z.number().optional()
1555
1378
  }).strict();
1556
- var replicaTableSchema = import_zod2.z.object({
1557
- label: import_zod2.z.string().optional(),
1558
- columns: import_zod2.z.record(import_zod2.z.string(), replicaColumnSchema).optional(),
1559
- rows: import_zod2.z.array(emulatorRowSchema).optional(),
1560
- rowCount: import_zod2.z.number().optional(),
1561
- dataOmitted: import_zod2.z.boolean().optional(),
1562
- dataOmittedReason: import_zod2.z.string().optional(),
1563
- error: import_zod2.z.string().optional()
1379
+ var replicaTableSchema = import_zod.z.object({
1380
+ label: import_zod.z.string().optional(),
1381
+ columns: import_zod.z.record(import_zod.z.string(), replicaColumnSchema).optional(),
1382
+ rows: import_zod.z.array(emulatorRowSchema).optional(),
1383
+ rowCount: import_zod.z.number().optional(),
1384
+ dataOmitted: import_zod.z.boolean().optional(),
1385
+ dataOmittedReason: import_zod.z.string().optional(),
1386
+ error: import_zod.z.string().optional()
1564
1387
  }).strict();
1565
- var replicaSelectionSchema = import_zod2.z.object({
1566
- tableId: import_zod2.z.string().optional(),
1567
- rowId: import_zod2.z.union([import_zod2.z.number(), import_zod2.z.literal("new"), import_zod2.z.null()]).optional(),
1568
- mappings: import_zod2.z.unknown().nullable().optional(),
1569
- cursor: import_zod2.z.unknown().optional()
1388
+ var replicaSelectionSchema = import_zod.z.object({
1389
+ tableId: import_zod.z.string().optional(),
1390
+ rowId: import_zod.z.union([import_zod.z.number(), import_zod.z.literal("new"), import_zod.z.null()]).optional(),
1391
+ mappings: import_zod.z.unknown().nullable().optional(),
1392
+ cursor: import_zod.z.unknown().optional()
1570
1393
  }).strict();
1571
- var replicaWidgetConfigurationSchema = import_zod2.z.object({
1572
- requiredAccess: import_zod2.z.string().optional(),
1573
- columns: import_zod2.z.unknown().optional(),
1574
- allowSelectBy: import_zod2.z.boolean().optional(),
1575
- hasCustomOptions: import_zod2.z.boolean().optional()
1394
+ var replicaWidgetConfigurationSchema = import_zod.z.object({
1395
+ requiredAccess: import_zod.z.string().optional(),
1396
+ columns: import_zod.z.unknown().optional(),
1397
+ allowSelectBy: import_zod.z.boolean().optional(),
1398
+ hasCustomOptions: import_zod.z.boolean().optional()
1576
1399
  }).strict();
1577
- var replicaAccessTokenSchema = import_zod2.z.object({
1578
- token: import_zod2.z.string(),
1579
- baseUrl: import_zod2.z.string(),
1580
- ttlMsecs: import_zod2.z.number()
1400
+ var replicaAccessTokenSchema = import_zod.z.object({
1401
+ token: import_zod.z.string(),
1402
+ baseUrl: import_zod.z.string(),
1403
+ ttlMsecs: import_zod.z.number()
1581
1404
  }).strict();
1582
- var replicaDocumentSchema = import_zod2.z.object({
1583
- generatedAt: import_zod2.z.string(),
1584
- source: import_zod2.z.string().optional(),
1585
- docName: import_zod2.z.string().optional(),
1586
- mode: import_zod2.z.enum(["schema-only", "schema+samples", "schema+data"]).optional(),
1587
- tables: import_zod2.z.record(import_zod2.z.string(), replicaTableSchema),
1405
+ var replicaDocumentSchema = import_zod.z.object({
1406
+ generatedAt: import_zod.z.string(),
1407
+ source: import_zod.z.string().optional(),
1408
+ docName: import_zod.z.string().optional(),
1409
+ mode: import_zod.z.enum(["schema-only", "schema+samples", "schema+data"]).optional(),
1410
+ tables: import_zod.z.record(import_zod.z.string(), replicaTableSchema),
1588
1411
  selection: replicaSelectionSchema.optional(),
1589
- widgetOptions: import_zod2.z.record(import_zod2.z.string(), import_zod2.z.unknown()).optional(),
1590
- widgetInteraction: import_zod2.z.unknown().optional(),
1412
+ widgetOptions: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional(),
1413
+ widgetInteraction: import_zod.z.unknown().optional(),
1591
1414
  configuration: replicaWidgetConfigurationSchema.optional(),
1592
1415
  accessToken: replicaAccessTokenSchema.nullable().optional()
1593
1416
  }).strict().refine((value) => Object.keys(value.tables).length > 0, {
@@ -1618,34 +1441,21 @@ function createGristEmulator(options) {
1618
1441
  const state = createDocument({
1619
1442
  ...validatedDocument,
1620
1443
  widgetOptions: options.widgetOptions ?? validatedDocument.widgetOptions,
1621
- widgetInteraction: options.widgetInteraction ?? validatedDocument.widgetInteraction ?? { access_level: "read table" }
1444
+ // No access_level by default: the emulator then grants the access the
1445
+ // widget asks for in grist.ready(), as a user accepting it in Grist would.
1446
+ widgetInteraction: options.widgetInteraction ?? validatedDocument.widgetInteraction ?? {}
1622
1447
  });
1623
1448
  const bus = createEventBus();
1624
1449
  const api = createApiBundle(state, bus);
1625
- const transportKind = options.transport?.kind ?? "inline";
1626
- let transport;
1627
- let grist;
1628
- if (transportKind === "iframe") {
1629
- if (options.transport?.kind !== "iframe") throw new Error("transport mismatch");
1630
- transport = createIframeTransport({
1631
- iframe: options.transport.iframe,
1632
- targetOrigin: options.transport.targetOrigin,
1633
- state,
1634
- bus,
1635
- api,
1636
- initialTheme: options.initialTheme
1637
- });
1638
- } else {
1639
- const inline = createInlineTransport({
1640
- state,
1641
- bus,
1642
- api,
1643
- initialTheme: options.initialTheme,
1644
- target: options.transport?.kind === "inline" ? options.transport.target : void 0
1645
- });
1646
- transport = inline;
1647
- grist = inline.grist;
1648
- }
1450
+ const inline = createInlineTransport({
1451
+ state,
1452
+ bus,
1453
+ api,
1454
+ initialTheme: options.initialTheme,
1455
+ target: options.transport?.target
1456
+ });
1457
+ const transport = inline;
1458
+ const grist = inline.grist;
1649
1459
  function apply(actions) {
1650
1460
  return applyUserActions(state, bus, actions);
1651
1461
  }