@scm-js/plugin-api 1.29.0 → 1.31.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.
- package/index.d.ts +291 -28
- package/package.json +1 -1
package/index.d.ts
CHANGED
|
@@ -1694,7 +1694,6 @@ export interface ChangeTilesetResult {
|
|
|
1694
1694
|
spritesDropped: number;
|
|
1695
1695
|
refilled: boolean;
|
|
1696
1696
|
}
|
|
1697
|
-
export type SymmetryMode = "none" | "h" | "v" | "hv" | "rot180" | "rot90" | "diag" | "adiag";
|
|
1698
1697
|
export interface DoodadChange {
|
|
1699
1698
|
index: number;
|
|
1700
1699
|
before: DoodadRecord | null;
|
|
@@ -1715,32 +1714,6 @@ export interface DoodadVerdict {
|
|
|
1715
1714
|
/** Cell indices (row-major) whose ground fails the check, for the ghost to mark red. */
|
|
1716
1715
|
bad: number[];
|
|
1717
1716
|
}
|
|
1718
|
-
export type StartLayout = "ring" | "corners";
|
|
1719
|
-
export interface StartPlacementResult {
|
|
1720
|
-
changes: UnitChange[];
|
|
1721
|
-
/** Per player (0-based), where the start location landed, or null when nothing within reach fit. */
|
|
1722
|
-
placed: ({
|
|
1723
|
-
x: number;
|
|
1724
|
-
y: number;
|
|
1725
|
-
} | null)[];
|
|
1726
|
-
removed: number;
|
|
1727
|
-
}
|
|
1728
|
-
export type Side = "left" | "top" | "right" | "bottom";
|
|
1729
|
-
export interface BlendCandidate {
|
|
1730
|
-
/** MTXM tile id to place. */
|
|
1731
|
-
id: number;
|
|
1732
|
-
megatile: number;
|
|
1733
|
-
/** `edgeDistance` between the anchor's side and this tile's opposite side. */
|
|
1734
|
-
distance: number;
|
|
1735
|
-
}
|
|
1736
|
-
export interface BlendOptions {
|
|
1737
|
-
/** Largest distance still listed. */
|
|
1738
|
-
maxDistance: number;
|
|
1739
|
-
/** Most candidates listed per side. */
|
|
1740
|
-
limit: number;
|
|
1741
|
-
/** Optional filter over tile ids (the palette's group-kind dropdown). */
|
|
1742
|
-
include?: (id: number) => boolean;
|
|
1743
|
-
}
|
|
1744
1717
|
/** The change lists of one edit; `HistoryEntry` adds the label. */
|
|
1745
1718
|
export interface HistoryEdit {
|
|
1746
1719
|
changes: TileChange[];
|
|
@@ -1780,6 +1753,128 @@ export interface HistoryEdit {
|
|
|
1780
1753
|
*/
|
|
1781
1754
|
createdMask?: Uint8Array;
|
|
1782
1755
|
}
|
|
1756
|
+
export interface WireListChange<T> {
|
|
1757
|
+
index: number;
|
|
1758
|
+
before: T | null;
|
|
1759
|
+
after: T | null;
|
|
1760
|
+
}
|
|
1761
|
+
export interface WireLocationChange {
|
|
1762
|
+
index: number;
|
|
1763
|
+
after: LocationRecord;
|
|
1764
|
+
/** The name as text (`after.nameIndex` is resolved again where it lands); null for none. */
|
|
1765
|
+
name: string | null;
|
|
1766
|
+
/** The slot was empty before: a new location, which moves to a free slot if someone else took this one. */
|
|
1767
|
+
created: boolean;
|
|
1768
|
+
}
|
|
1769
|
+
/**
|
|
1770
|
+
* One undo step's worth of terrain and objects. The grids are flat number lists: terrain
|
|
1771
|
+
* `[at, mtxm, tile, …]`, the others `[at, value, …]`. `reverse` marks the undo of an edit:
|
|
1772
|
+
* its parts go in the reverse of the usual order, as undo walks them.
|
|
1773
|
+
*/
|
|
1774
|
+
export interface SyncEditOp {
|
|
1775
|
+
kind: "edit";
|
|
1776
|
+
label: string;
|
|
1777
|
+
/** The map size the cells index; a grid part is skipped on a map of another size. */
|
|
1778
|
+
width: number;
|
|
1779
|
+
height: number;
|
|
1780
|
+
reverse?: boolean;
|
|
1781
|
+
terrain?: number[];
|
|
1782
|
+
isom?: number[];
|
|
1783
|
+
doodadTiles?: number[];
|
|
1784
|
+
fog?: number[];
|
|
1785
|
+
/** The whole ISOM lattice (base64 of the u16 cells) the edit gave the map, or null for taking it away. */
|
|
1786
|
+
isomWhole?: string | null;
|
|
1787
|
+
maskWhole?: string | null;
|
|
1788
|
+
units?: WireListChange<UnitRecord>[];
|
|
1789
|
+
doodads?: WireListChange<DoodadRecord>[];
|
|
1790
|
+
sprites?: WireListChange<SpriteRecord>[];
|
|
1791
|
+
locations?: WireLocationChange[];
|
|
1792
|
+
}
|
|
1793
|
+
declare const SYNC_FIELDS: readonly [
|
|
1794
|
+
"type",
|
|
1795
|
+
"fileVersion",
|
|
1796
|
+
"nameIndex",
|
|
1797
|
+
"descriptionIndex",
|
|
1798
|
+
"playerTypes",
|
|
1799
|
+
"editorPlayerTypes",
|
|
1800
|
+
"playerRaces",
|
|
1801
|
+
"playerColors",
|
|
1802
|
+
"playerRgb",
|
|
1803
|
+
"forces",
|
|
1804
|
+
"unitSettings",
|
|
1805
|
+
"unitAvailability",
|
|
1806
|
+
"upgradeSettings",
|
|
1807
|
+
"upgradeRestrictions",
|
|
1808
|
+
"techSettings",
|
|
1809
|
+
"techRestrictions",
|
|
1810
|
+
"wavs",
|
|
1811
|
+
"cuwp",
|
|
1812
|
+
"cuwpUsed",
|
|
1813
|
+
"triggers",
|
|
1814
|
+
"briefing",
|
|
1815
|
+
"switchNames"
|
|
1816
|
+
];
|
|
1817
|
+
export type SyncField = (typeof SYNC_FIELDS)[number];
|
|
1818
|
+
export interface SyncFieldsOp {
|
|
1819
|
+
kind: "fields";
|
|
1820
|
+
/** Each changed field's new value, packed (`pack`). */
|
|
1821
|
+
set: Partial<Record<SyncField, unknown>>;
|
|
1822
|
+
/** String slots that changed: `[index, before, after]`. */
|
|
1823
|
+
strings?: [
|
|
1824
|
+
number,
|
|
1825
|
+
string | null,
|
|
1826
|
+
string | null
|
|
1827
|
+
][];
|
|
1828
|
+
/** The table's length afterwards (trailing blank slots are dropped). */
|
|
1829
|
+
stringsLength?: number;
|
|
1830
|
+
/** The table's width and text encoding, when they changed. */
|
|
1831
|
+
stringsFormat?: {
|
|
1832
|
+
extended: boolean;
|
|
1833
|
+
encoding: TextEncoding;
|
|
1834
|
+
};
|
|
1835
|
+
}
|
|
1836
|
+
export interface SyncExtrasOp {
|
|
1837
|
+
kind: "extras";
|
|
1838
|
+
/** `[member name, base64 bytes]`, or null bytes for a member taken out. */
|
|
1839
|
+
set: [
|
|
1840
|
+
string,
|
|
1841
|
+
string | null
|
|
1842
|
+
][];
|
|
1843
|
+
}
|
|
1844
|
+
export interface SyncResetOp {
|
|
1845
|
+
kind: "reset";
|
|
1846
|
+
label: string;
|
|
1847
|
+
/** The whole scenario, base64. */
|
|
1848
|
+
chk: string;
|
|
1849
|
+
}
|
|
1850
|
+
export type SyncOp = SyncEditOp | SyncFieldsOp | SyncExtrasOp | SyncResetOp;
|
|
1851
|
+
export type SymmetryMode = "none" | "h" | "v" | "hv" | "rot180" | "rot90" | "diag" | "adiag";
|
|
1852
|
+
export type StartLayout = "ring" | "corners";
|
|
1853
|
+
export interface StartPlacementResult {
|
|
1854
|
+
changes: UnitChange[];
|
|
1855
|
+
/** Per player (0-based), where the start location landed, or null when nothing within reach fit. */
|
|
1856
|
+
placed: ({
|
|
1857
|
+
x: number;
|
|
1858
|
+
y: number;
|
|
1859
|
+
} | null)[];
|
|
1860
|
+
removed: number;
|
|
1861
|
+
}
|
|
1862
|
+
export type Side = "left" | "top" | "right" | "bottom";
|
|
1863
|
+
export interface BlendCandidate {
|
|
1864
|
+
/** MTXM tile id to place. */
|
|
1865
|
+
id: number;
|
|
1866
|
+
megatile: number;
|
|
1867
|
+
/** `edgeDistance` between the anchor's side and this tile's opposite side. */
|
|
1868
|
+
distance: number;
|
|
1869
|
+
}
|
|
1870
|
+
export interface BlendOptions {
|
|
1871
|
+
/** Largest distance still listed. */
|
|
1872
|
+
maxDistance: number;
|
|
1873
|
+
/** Most candidates listed per side. */
|
|
1874
|
+
limit: number;
|
|
1875
|
+
/** Optional filter over tile ids (the palette's group-kind dropdown). */
|
|
1876
|
+
include?: (id: number) => boolean;
|
|
1877
|
+
}
|
|
1783
1878
|
export type ClipPart = "terrain" | "doodads" | "units" | "sprites" | "locations" | "fog";
|
|
1784
1879
|
export type ClipParts = Record<ClipPart, boolean>;
|
|
1785
1880
|
/** Whether a paste adds to what is in the target area or clears its objects first. */
|
|
@@ -1855,11 +1950,55 @@ export interface Preferences {
|
|
|
1855
1950
|
* is replaced by the first map opened rather than kept beside it.
|
|
1856
1951
|
*/
|
|
1857
1952
|
multipleMaps: boolean;
|
|
1858
|
-
/**
|
|
1953
|
+
/**
|
|
1954
|
+
* What File ▸ New and the startup map start with. `version` is the file's revision:
|
|
1955
|
+
* Brood War (VER 205, what every build reads) or Remastered (206, 32-bit strings).
|
|
1956
|
+
*/
|
|
1859
1957
|
newMap: {
|
|
1860
1958
|
tileset: TilesetId;
|
|
1861
1959
|
width: number;
|
|
1862
1960
|
height: number;
|
|
1961
|
+
version: NewMapVersion;
|
|
1962
|
+
};
|
|
1963
|
+
/**
|
|
1964
|
+
* Startup: `reopenLast` opens the most recent file again in place of the blank map
|
|
1965
|
+
* (the desktop app straight away; a browser needs a click first, so it asks in a
|
|
1966
|
+
* notice), and `recents` is how many File ▸ Open Recent keeps.
|
|
1967
|
+
*/
|
|
1968
|
+
startup: {
|
|
1969
|
+
reopenLast: boolean;
|
|
1970
|
+
recents: number;
|
|
1971
|
+
};
|
|
1972
|
+
/**
|
|
1973
|
+
* What the Save dialog starts from: the options the file was opened with
|
|
1974
|
+
* (`"asOpened"`, the default), or one of its presets. `compression` is for a map with
|
|
1975
|
+
* no origin — new, or opened from a bare .chk — where there is nothing to follow;
|
|
1976
|
+
* `"asOpened"` there means StarEdit's PKWARE.
|
|
1977
|
+
*/
|
|
1978
|
+
save: {
|
|
1979
|
+
start: "asOpened" | "everything" | "smallest";
|
|
1980
|
+
compression: "asOpened" | ArchiveCompression;
|
|
1981
|
+
};
|
|
1982
|
+
/** How many edits Undo keeps per map (SCMDraft keeps 200). */
|
|
1983
|
+
undoLevels: number;
|
|
1984
|
+
/**
|
|
1985
|
+
* The mouse wheel over the map: scrolling, with Ctrl+wheel zooming (the default), or
|
|
1986
|
+
* zooming, with Shift+wheel scrolling sideways. `zoomToCursor` keeps the tile under the
|
|
1987
|
+
* pointer in place when the wheel zooms; the menu and keyboard keep the centre.
|
|
1988
|
+
*/
|
|
1989
|
+
view: {
|
|
1990
|
+
wheel: "scroll" | "zoom";
|
|
1991
|
+
zoomToCursor: boolean;
|
|
1992
|
+
};
|
|
1993
|
+
/**
|
|
1994
|
+
* What the palettes start on: the owner of placed units and sprites (0 = player 1),
|
|
1995
|
+
* the brush size (1–7), and the size of a location the Locations palette's New makes,
|
|
1996
|
+
* in tiles.
|
|
1997
|
+
*/
|
|
1998
|
+
placement: {
|
|
1999
|
+
owner: number;
|
|
2000
|
+
brushSize: number;
|
|
2001
|
+
locationTiles: number;
|
|
1863
2002
|
};
|
|
1864
2003
|
/** Initial View ▸ Animate Water / Animate Units. */
|
|
1865
2004
|
animateWater: boolean;
|
|
@@ -1912,6 +2051,8 @@ export interface Preferences {
|
|
|
1912
2051
|
}
|
|
1913
2052
|
/** See `Preferences.plugins.updates`. */
|
|
1914
2053
|
export type PluginUpdateMode = "notify" | "manual" | "auto";
|
|
2054
|
+
/** The revisions a new map can start on; the two older ones are for opening old files, not making new ones. */
|
|
2055
|
+
export type NewMapVersion = Extract<MapVersion, "broodwar" | "remastered">;
|
|
1915
2056
|
/**
|
|
1916
2057
|
* The version a host provides; a manifest that asks for a newer one is refused. It stays
|
|
1917
2058
|
* at 1 while the API is only used by the plugins in the scm-js organisation and grows
|
|
@@ -2075,6 +2216,13 @@ export interface PluginApi {
|
|
|
2075
2216
|
/** The plugin's words in the editor's language: its own catalogues, and a `t` over them. */
|
|
2076
2217
|
readonly i18n: I18nApi;
|
|
2077
2218
|
readonly events: EventsApi;
|
|
2219
|
+
/**
|
|
2220
|
+
* Editing the map in front together with other editors: every change to it is handed out
|
|
2221
|
+
* as an op for the plugin to carry to them, and theirs come back in, in the order a
|
|
2222
|
+
* server puts them. The scmjs.dev plugin's shared maps are built on it; the transport,
|
|
2223
|
+
* the server and who may join are the plugin's.
|
|
2224
|
+
*/
|
|
2225
|
+
readonly sync: SyncApi;
|
|
2078
2226
|
/**
|
|
2079
2227
|
* A small key-value store of the plugin's own, kept in the browser's local storage under
|
|
2080
2228
|
* the plugin's id and listed with everything else in Preferences ▸ Browser storage.
|
|
@@ -4077,6 +4225,40 @@ export interface DialogSlotHost {
|
|
|
4077
4225
|
export interface DialogSlotSpec {
|
|
4078
4226
|
mount(body: HTMLElement, host: DialogSlotHost): void | (() => void);
|
|
4079
4227
|
}
|
|
4228
|
+
/**
|
|
4229
|
+
* A page of the plugin's own in Edit ▸ Preferences, listed under Plugins by the plugin's
|
|
4230
|
+
* name — where a user looks for a setting, instead of a menu item per plugin. One page per
|
|
4231
|
+
* plugin; registering again replaces it. `mount` runs the first time the page is shown
|
|
4232
|
+
* while the dialog is open and the page then stays mounted until the dialog closes, so
|
|
4233
|
+
* what the user changed is still there when OK or Apply calls `apply`; the cleanup runs
|
|
4234
|
+
* on close. A page with no `apply` writes its settings as they change (`api.storage`),
|
|
4235
|
+
* which is the simpler shape when nothing needs undoing on Cancel.
|
|
4236
|
+
*
|
|
4237
|
+
* @example
|
|
4238
|
+
* api.ui.preferencesPage({
|
|
4239
|
+
* mount(body) {
|
|
4240
|
+
* const w = api.ui.widgets;
|
|
4241
|
+
* const dock = w.select([{ value: "float", label: "Floating" }, { value: "right", label: "Docked" }], { value: settings.dock });
|
|
4242
|
+
* body.append(w.form([{ label: "Panel", field: dock }]), w.hint("Where the panel opens."));
|
|
4243
|
+
* pending = () => save({ dock: dock.value });
|
|
4244
|
+
* },
|
|
4245
|
+
* apply: () => pending?.(),
|
|
4246
|
+
* });
|
|
4247
|
+
*/
|
|
4248
|
+
export interface PreferencesPageSpec {
|
|
4249
|
+
/** Fill `body` with the page's controls; return a cleanup if you need one. */
|
|
4250
|
+
mount(body: HTMLElement, page: PreferencesPageHost): void | (() => void);
|
|
4251
|
+
/** Called on OK and Apply while the dialog is open and the page has been shown. */
|
|
4252
|
+
apply?(): void;
|
|
4253
|
+
/** Called by Reset to defaults while the page is the one showing. */
|
|
4254
|
+
reset?(): void;
|
|
4255
|
+
}
|
|
4256
|
+
/** What a preferences page's `mount` is handed. */
|
|
4257
|
+
export interface PreferencesPageHost {
|
|
4258
|
+
readonly plugin: PluginInfo;
|
|
4259
|
+
/** Close the Preferences dialog. */
|
|
4260
|
+
close(): void;
|
|
4261
|
+
}
|
|
4080
4262
|
/**
|
|
4081
4263
|
* What `view.flash` highlights: a tile rect, or units / locations by index. The
|
|
4082
4264
|
* highlight fades over `ms` (600 by default) and never blocks or takes the pointer; it is
|
|
@@ -4237,6 +4419,11 @@ export interface PickFilesOptions {
|
|
|
4237
4419
|
multiple?: boolean;
|
|
4238
4420
|
}
|
|
4239
4421
|
export interface UiApi {
|
|
4422
|
+
/**
|
|
4423
|
+
* The dialogs open now, bottom to top, by id: the built-in ones by their `DialogId`, a
|
|
4424
|
+
* plugin's as `"pluginDialog"`. What a shared map shows others ("in Player Settings").
|
|
4425
|
+
*/
|
|
4426
|
+
openDialogs(): string[];
|
|
4240
4427
|
/** Set the status bar line. */
|
|
4241
4428
|
status(text: string): void;
|
|
4242
4429
|
/** The status bar line as it stands. */
|
|
@@ -4286,6 +4473,12 @@ export interface UiApi {
|
|
|
4286
4473
|
* });
|
|
4287
4474
|
*/
|
|
4288
4475
|
dialogSlot(dialog: DialogSlotId, spec: DialogSlotSpec): Disposable;
|
|
4476
|
+
/**
|
|
4477
|
+
* A page of the plugin's own in Edit ▸ Preferences, under Plugins — see
|
|
4478
|
+
* `PreferencesPageSpec`. `ui.open("preferences", { page: "plugin:<id>" })` opens it.
|
|
4479
|
+
* Returns the registration; it leaves with `dispose()` or the plugin.
|
|
4480
|
+
*/
|
|
4481
|
+
preferencesPage(spec: PreferencesPageSpec): Disposable;
|
|
4289
4482
|
/**
|
|
4290
4483
|
* Take over the pointer on the map until `stop()`, Esc, a right-click, a map change
|
|
4291
4484
|
* or another tool. One tool runs at a time — starting one stops the previous — and a
|
|
@@ -4778,8 +4971,78 @@ export type PluginEvent =
|
|
|
4778
4971
|
| "commands"
|
|
4779
4972
|
/** A plugin provided or withdrew a service — `services.watch` is the usual way to hear this for one name. */
|
|
4780
4973
|
| "services"
|
|
4974
|
+
/** A dialog opened or closed — `ui.openDialogs()` says which are open. */
|
|
4975
|
+
| "dialogs"
|
|
4781
4976
|
/** The game data source changed: installed, switched to another data set, or a copy removed. `gameData.source()` says what it is now. */
|
|
4782
4977
|
| "gameData";
|
|
4978
|
+
/** Why other people's changes are waiting: a stroke under way on the map, a dialog that edits the map open, another map in front. */
|
|
4979
|
+
export type SyncHold = "stroke" | "dialog" | "behind";
|
|
4980
|
+
export interface SyncReport {
|
|
4981
|
+
/** Other people's ops applied. */
|
|
4982
|
+
ops: number;
|
|
4983
|
+
/** Parts of them that could not be applied — the unit they moved was deleted, the map was resized… */
|
|
4984
|
+
dropped: number;
|
|
4985
|
+
/** Parts of this editor's unconfirmed changes that no longer applied on top of theirs. */
|
|
4986
|
+
lost: number;
|
|
4987
|
+
}
|
|
4988
|
+
export interface SyncStartOptions {
|
|
4989
|
+
/**
|
|
4990
|
+
* Every change made to the shared map, as a plain object JSON can carry, in the order
|
|
4991
|
+
* made: commits, undo and redo, the dialogs' writes, archive files, and a resize or a
|
|
4992
|
+
* tileset change (the whole scenario). Each must reach the server, and every other
|
|
4993
|
+
* editor, in this order.
|
|
4994
|
+
*/
|
|
4995
|
+
send(op: SyncOp): void;
|
|
4996
|
+
/** The session ended: the shared map was closed or replaced by another, or `stop` was called. */
|
|
4997
|
+
onEnd?(reason: "closed" | "stopped"): void;
|
|
4998
|
+
/** Other people's changes were applied to the map. */
|
|
4999
|
+
onApplied?(report: SyncReport): void;
|
|
5000
|
+
}
|
|
5001
|
+
/**
|
|
5002
|
+
* One shared map. The server confirms each op this editor sent (`confirm`, in order) and
|
|
5003
|
+
* relays everyone else's (`receive`, in the order it gave them); the session applies
|
|
5004
|
+
* them to the map when nothing is in the way (`holding`) and rebases this editor's
|
|
5005
|
+
* unconfirmed changes on top, so every editor that has seen the same ops has the same map.
|
|
5006
|
+
*
|
|
5007
|
+
* @example
|
|
5008
|
+
* const session = api.sync.start({ send: (op) => socket.send(JSON.stringify({ type: "op", op })) });
|
|
5009
|
+
* socket.onmessage = (m) => {
|
|
5010
|
+
* const msg = JSON.parse(m.data);
|
|
5011
|
+
* if (msg.type === "ack") session?.confirm();
|
|
5012
|
+
* if (msg.type === "op") session?.receive(msg.op);
|
|
5013
|
+
* };
|
|
5014
|
+
*/
|
|
5015
|
+
export interface SyncSession {
|
|
5016
|
+
/** The shared map's `document.id()`. */
|
|
5017
|
+
readonly documentId: number;
|
|
5018
|
+
/** Another editor's op, in the server's order. False, and nothing done, for a value that is not an op. */
|
|
5019
|
+
receive(op: unknown): boolean;
|
|
5020
|
+
/** The server confirmed the oldest op this editor sent. */
|
|
5021
|
+
confirm(): void;
|
|
5022
|
+
/** Ops sent and not yet confirmed. */
|
|
5023
|
+
pending(): number;
|
|
5024
|
+
/** Ops and confirmations received and not yet applied. */
|
|
5025
|
+
waiting(): number;
|
|
5026
|
+
/**
|
|
5027
|
+
* The shared map as a file (`.scx` / `.scm`, not built), copied at this moment — for a
|
|
5028
|
+
* server to hand people joining, labelled with the last op this editor applied. Null
|
|
5029
|
+
* while anything is pending or waiting (the copy would not match any point in the
|
|
5030
|
+
* server's order) or while another map is in front. The copy is taken before the first
|
|
5031
|
+
* `await`, so a change made while the file is written is not in it.
|
|
5032
|
+
*/
|
|
5033
|
+
snapshot(): Promise<Uint8Array | null>;
|
|
5034
|
+
holding(): SyncHold | null;
|
|
5035
|
+
stop(): void;
|
|
5036
|
+
}
|
|
5037
|
+
export interface SyncApi {
|
|
5038
|
+
/**
|
|
5039
|
+
* Share the map in front. Null when no map is open or another session is running (one
|
|
5040
|
+
* at a time). A session a plugin started stops when the plugin is turned off.
|
|
5041
|
+
*/
|
|
5042
|
+
start(options: SyncStartOptions): SyncSession | null;
|
|
5043
|
+
/** The running session, whichever plugin started it. */
|
|
5044
|
+
current(): SyncSession | null;
|
|
5045
|
+
}
|
|
4783
5046
|
/** Why the document changed. */
|
|
4784
5047
|
export type DocumentChangeReason =
|
|
4785
5048
|
/** File ▸ Open, drag-and-drop, `document.open`: a file the user chose. */
|