@mudlet/mudlet-web 0.3.0 → 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 (111) hide show
  1. package/LICENSE +338 -338
  2. package/README.md +158 -150
  3. package/dist-lib/assets/qt-resources/index.d.ts +16 -0
  4. package/dist-lib/branding.d.ts +1 -1
  5. package/dist-lib/hooks/useMudSession.d.ts +1 -1
  6. package/dist-lib/hooks/useOutput.d.ts +3 -1
  7. package/dist-lib/import/defaultPackages.d.ts +35 -2
  8. package/dist-lib/import/defaults/generic_mapper/generic_mapper.mpackage +0 -0
  9. package/dist-lib/import/defaults/gui-drop/gui-drop.mpackage +0 -0
  10. package/dist-lib/import/defaults/{run-lua-code.mpackage → mudlet-base-ui/mudlet-base-ui.mpackage} +0 -0
  11. package/dist-lib/import/defaults/mudlet-mapper.xml +13023 -13023
  12. package/dist-lib/import/defaults/run-lua-code/run-lua-code.mpackage +0 -0
  13. package/dist-lib/import/packageExport.d.ts +71 -0
  14. package/dist-lib/import/packageInstaller.d.ts +4 -1
  15. package/dist-lib/index.js +45910 -39931
  16. package/dist-lib/logging/SessionLogger.d.ts +66 -1
  17. package/dist-lib/map/MapStore.d.ts +32 -4
  18. package/dist-lib/map/mapImageExport.d.ts +1 -1
  19. package/dist-lib/map/mapZoom.d.ts +71 -0
  20. package/dist-lib/mud/ItemIdSequence.d.ts +23 -0
  21. package/dist-lib/mud/MudSession.d.ts +113 -11
  22. package/dist-lib/mud/PatternEngine.d.ts +6 -1
  23. package/dist-lib/mud/commandSplit.d.ts +16 -0
  24. package/dist-lib/mud/connection/LineAssembler.d.ts +56 -0
  25. package/dist-lib/mud/connection/MudClient.d.ts +136 -19
  26. package/dist-lib/mud/connection/TelnetNegotiator.d.ts +39 -0
  27. package/dist-lib/mud/connection/telnetByteTags.d.ts +35 -0
  28. package/dist-lib/mud/events.d.ts +86 -4
  29. package/dist-lib/mud/games/bundledGames.d.ts +29 -0
  30. package/dist-lib/mud/games/gameIcons.d.ts +5 -0
  31. package/dist-lib/mud/keybindings/KeyEngine.d.ts +9 -2
  32. package/dist-lib/mud/keybindings/browserReservedKeys.d.ts +5 -1
  33. package/dist-lib/mud/keybindings/keyEventTarget.d.ts +17 -0
  34. package/dist-lib/mud/keybindings/qtKeys.d.ts +2 -0
  35. package/dist-lib/mud/protocol/charLoginFlow.d.ts +68 -0
  36. package/dist-lib/mud/protocol/charset.d.ts +27 -5
  37. package/dist-lib/mud/protocol/constants.d.ts +1 -0
  38. package/dist-lib/mud/protocol/gmcp.d.ts +19 -3
  39. package/dist-lib/mud/protocol/index.d.ts +1 -1
  40. package/dist-lib/mud/protocol/msspTls.d.ts +54 -0
  41. package/dist-lib/mud/protocol/mxp.d.ts +28 -5
  42. package/dist-lib/mud/protocol/tlsCodes.d.ts +4 -0
  43. package/dist-lib/mud/replay/ReplayPlayer.d.ts +17 -0
  44. package/dist-lib/mud/text/Console.d.ts +64 -0
  45. package/dist-lib/mud/text/FormatState.d.ts +66 -3
  46. package/dist-lib/mud/text/hyperlinkVisibility.d.ts +35 -0
  47. package/dist-lib/mud/text/osc8Docs.d.ts +23 -0
  48. package/dist-lib/mud/text/serverWrap.d.ts +191 -0
  49. package/dist-lib/mud/timers/TimerEngine.d.ts +43 -12
  50. package/dist-lib/mud/triggers/TriggerEngine.d.ts +112 -3
  51. package/dist-lib/scripting/EngineHost.d.ts +56 -3
  52. package/dist-lib/scripting/IScriptingRuntime.d.ts +22 -0
  53. package/dist-lib/scripting/MxpFrameManager.d.ts +118 -0
  54. package/dist-lib/scripting/ScriptingAPI.d.ts +331 -54
  55. package/dist-lib/scripting/ScriptingEngine.d.ts +181 -24
  56. package/dist-lib/scripting/http/HttpService.d.ts +51 -1
  57. package/dist-lib/scripting/lua/LuaRuntime.d.ts +159 -2
  58. package/dist-lib/scripting/lua/bindings/map.d.ts +0 -12
  59. package/dist-lib/scripting/lua/utf8Patterns.d.ts +59 -0
  60. package/dist-lib/storage/index.d.ts +1 -1
  61. package/dist-lib/storage/persistentStorage.d.ts +10 -0
  62. package/dist-lib/storage/schema.d.ts +166 -4
  63. package/dist-lib/styles.css +1 -1
  64. package/dist-lib/ui/BundledGameGrid.d.ts +21 -0
  65. package/dist-lib/ui/CharLoginModal.d.ts +5 -1
  66. package/dist-lib/ui/CommandBar.d.ts +7 -1
  67. package/dist-lib/ui/ConnectionGrid.d.ts +4 -1
  68. package/dist-lib/ui/FileBrowserModal.d.ts +1 -0
  69. package/dist-lib/ui/FilePickerModal.d.ts +3 -3
  70. package/dist-lib/ui/HelpModal.d.ts +11 -0
  71. package/dist-lib/ui/LogBrowserModal.d.ts +4 -1
  72. package/dist-lib/ui/PlayerMarkerPreview.d.ts +20 -0
  73. package/dist-lib/ui/SettingsModal.d.ts +5 -1
  74. package/dist-lib/ui/TlsAlertBanner.d.ts +18 -0
  75. package/dist-lib/ui/TlsCertificateBox.d.ts +20 -0
  76. package/dist-lib/ui/TlsUpgradeModal.d.ts +16 -0
  77. package/dist-lib/ui/Toolbar.d.ts +2 -1
  78. package/dist-lib/ui/VfsPickerModal.d.ts +26 -0
  79. package/dist-lib/ui/components/FileSourceButton.d.ts +47 -0
  80. package/dist-lib/ui/components/index.d.ts +1 -0
  81. package/dist-lib/ui/components/useModalFocus.d.ts +6 -0
  82. package/dist-lib/ui/helpTopics.d.ts +19 -0
  83. package/dist-lib/ui/labels/LabelManager.d.ts +32 -0
  84. package/dist-lib/ui/labels/labelLinks.d.ts +52 -0
  85. package/dist-lib/ui/layout/ScriptWindow.d.ts +11 -2
  86. package/dist-lib/ui/markdown.d.ts +10 -1
  87. package/dist-lib/ui/output/OutputArea.d.ts +11 -1
  88. package/dist-lib/ui/output/OutputContextMenu.d.ts +3 -1
  89. package/dist-lib/ui/output/OutputRenderer.d.ts +7 -1
  90. package/dist-lib/ui/output/OutputSearchBar.d.ts +25 -0
  91. package/dist-lib/ui/output/StickyOutputPanel.d.ts +5 -1
  92. package/dist-lib/ui/output/linkNavigation.d.ts +22 -0
  93. package/dist-lib/ui/output/outputSearch.d.ts +130 -0
  94. package/dist-lib/ui/search/matcher.d.ts +15 -0
  95. package/dist-lib/ui/search/useDebounced.d.ts +3 -0
  96. package/dist-lib/ui/tts/TtsManager.d.ts +3 -0
  97. package/dist-lib/ui/useCommandHistory.d.ts +4 -2
  98. package/dist-lib/ui/useOpenProfiles.d.ts +26 -0
  99. package/dist-lib/ui/video/VideoManager.d.ts +17 -0
  100. package/dist-lib/ui/windows/WindowManager.d.ts +149 -2
  101. package/dist-lib/ui/windows/panels/MapPanel.d.ts +4 -1
  102. package/dist-lib/ui/windows/panels/PackageExportModal.d.ts +18 -0
  103. package/dist-lib/ui/windows/panels/TextPanel.d.ts +3 -1
  104. package/dist-lib/ui/windows/types.d.ts +35 -0
  105. package/dist-lib/utils/describeThrown.d.ts +22 -0
  106. package/dist-lib/utils/fontLoader.d.ts +5 -0
  107. package/dist-lib/vfs-sw.js +131 -131
  108. package/dist-lib/vite.js +22 -0
  109. package/package.json +97 -93
  110. package/dist-lib/scripting/lua/mudlet-lua/generic-mapper/generic_mapper.mpackage +0 -0
  111. package/dist-lib/ui/windows/panels/VfsModulePickerModal.d.ts +0 -8
@@ -11,7 +11,29 @@
11
11
  * the send/prompt/url action, reveals a spoiler, toggles selection, arms
12
12
  * visibility); the Menu key or Shift+F10 opens its context menu. Mirrors
13
13
  * Mudlet's TTextEdit key handling.
14
+ *
15
+ * Being focusable has a mouse-side cost, which `restoreFocusAfterLinkClick`
16
+ * pays back: a tabIndex of -1 keeps a span out of the Tab order but still lets
17
+ * a click focus it, and Mudlet's TTextEdit never takes focus off the command
18
+ * line that way.
19
+ */
20
+ /**
21
+ * Give focus back to the command line after a link is clicked with the mouse,
22
+ * so the player can keep typing — Mudlet's behaviour, where the output never
23
+ * takes focus at all.
24
+ *
25
+ * Call from a *capture*-phase click listener on the output container: a link's
26
+ * own handler stops propagation, so a bubble-phase listener never sees it. The
27
+ * hand-back is deferred to a microtask (i.e. after the click has been fully
28
+ * dispatched) and happens only if a link is still holding focus — a link that
29
+ * opened a dialog or moved focus deliberately keeps its say. Keyboard
30
+ * activation (`link.click()` from Enter/Space, which reports `detail === 0`)
31
+ * leaves focus on the link so Ctrl+]/Ctrl+[ can carry on from there.
14
32
  */
33
+ export declare function restoreFocusAfterLinkClick(e: {
34
+ detail: number;
35
+ target: EventTarget | null;
36
+ }, commandInput: () => HTMLElement | null | undefined): void;
15
37
  /**
16
38
  * One representative element per logical link under `root`, in document order.
17
39
  * A multicolour link is rendered as several spans (one per colour run) sharing
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Find-in-output: scans the rendered scrollback for a query and tints every hit.
3
+ *
4
+ * This works purely on the rendered DOM rather than on the `AnsiAwareBuffer`
5
+ * model behind each line. Two reasons:
6
+ *
7
+ * - Highlighting is transient. `buffer.applyFormat()` rewrites segment state,
8
+ * so undoing a search highlight would mean snapshotting and restoring every
9
+ * touched line's formatting — and a script that recolours a line mid-search
10
+ * would lose its change. Wrapping text nodes in `<mark>` leaves the model
11
+ * untouched, and unwrapping restores the DOM exactly.
12
+ * - Offsets line up by construction. `appendCells` can substitute text on the
13
+ * way to the DOM (tab-stop expansion, Mudlet control-character glyphs), so
14
+ * `buffer.text` offsets do not always index the rendered line. Searching the
15
+ * rendered text means we match what the player can actually see.
16
+ *
17
+ * The sticky split-view panel holds `cloneNode` copies of the last N lines, so
18
+ * it is not scanned — it lives outside the `.output-wrapper` scroller we walk.
19
+ * Re-populating it after highlighting copies the marks along with the lines.
20
+ */
21
+ import type { MatchRange, SearchMatcher } from '../search/matcher';
22
+ export declare const HIT_CLASS = "output-search-hit";
23
+ export declare const CURRENT_HIT_CLASS = "output-search-hit--current";
24
+ export interface OutputMatch {
25
+ /** The `.output-msg` line element containing this hit. */
26
+ line: HTMLElement;
27
+ /** Offsets into the line's rendered text — `[start, end)`. */
28
+ range: MatchRange;
29
+ }
30
+ /**
31
+ * Every match in the output, in document (i.e. chronological) order.
32
+ *
33
+ * Cheap enough to re-run on each keystroke: the per-line cost is one
34
+ * `textContent` read plus the matcher's scan, and only lines that actually hit
35
+ * get walked node-by-node later, in {@link applyHighlights}.
36
+ */
37
+ export declare function scanOutput(container: HTMLElement, matcher: SearchMatcher): OutputMatch[];
38
+ /**
39
+ * Tint every match, giving `matches[currentIndex]` the "current hit" treatment.
40
+ *
41
+ * Callers must {@link clearHighlights} first — marking an already-marked line
42
+ * would nest marks. Matches whose line has since been evicted from the DOM are
43
+ * skipped rather than treated as an error; the scrollback trims as new output
44
+ * arrives and a stale match is normal, not exceptional.
45
+ */
46
+ export declare function applyHighlights(matches: OutputMatch[], currentIndex: number): void;
47
+ /**
48
+ * Whether an Escape that reached the document should close the find bar.
49
+ *
50
+ * The bar's own input handles Escape directly, but the player usually leaves
51
+ * focus in the command line while reading results, and there Escape did nothing
52
+ * — the bar just stayed up. So the bar also listens on the document, in the
53
+ * BUBBLE phase, and defers to everything with a better claim on the key:
54
+ *
55
+ * - `defaultPrevented` — someone already acted on it. Covers the bar's own
56
+ * input, and layers correctly under CommandBar, which preventDefaults
57
+ * Escape only while an autocomplete ghost is showing: first Escape dismisses
58
+ * the ghost, a second one closes the bar.
59
+ * - Modals (`useModalFocus`) stopPropagation on their own node, and
60
+ * CaretReviewPanel uses capture + stopImmediatePropagation while focused, so
61
+ * neither ever reaches a document bubble listener in the first place.
62
+ * - The origin is then required to be inside `.app`, the profile-session shell
63
+ * that owns the bar — toolbar, output, command line, docked panels. Floating
64
+ * script windows portal to `<body>` instead (`.floating-window-root`), so
65
+ * they fall outside it structurally and keep their own Escape.
66
+ *
67
+ * Pure apart from reading the event's target, so it is unit-testable.
68
+ */
69
+ export declare function shouldCloseSearchOnEscape(e: KeyboardEvent): boolean;
70
+ /**
71
+ * Which way `e` steps through search results — `1` for F3, `-1` for Shift+F3,
72
+ * or null when it isn't the search-step key at all. Mudlet's binding
73
+ * (`TConsole::setF3SearchEnabled` wires F3 to slot_searchBufferDown and
74
+ * Shift+F3 to slot_searchBufferUp).
75
+ *
76
+ * Any other modifier disqualifies it, so Ctrl+F3 / Alt+F3 / Cmd+F3 stay
77
+ * available to the browser and the OS. Pure — reads only `key` and the modifier
78
+ * flags — and shared by the bar (which steps) and the output area (which opens
79
+ * the bar when it's closed), so the two can never drift apart.
80
+ */
81
+ export declare function searchStepDirection(e: KeyboardEvent): -1 | 1 | null;
82
+ /**
83
+ * The rendered text of a whole matched line, for announcing a hit to a screen
84
+ * reader. Mudlet's `focusOnSearchResultAndAnnounce` speaks the entire buffer
85
+ * line rather than the matched fragment, so a listener hears the hit in context.
86
+ * Excludes the timestamp column, like {@link scanOutput}.
87
+ */
88
+ export declare function matchLineText(match: OutputMatch): string;
89
+ /**
90
+ * Park keyboard focus on the current hit so a screen reader's virtual cursor
91
+ * lands *on* the match in the live output — the reachable half of Mudlet's
92
+ * `focusOnSearchResultAndAnnounce`, which moves its caret to the found column.
93
+ *
94
+ * The mark is made programmatically focusable (`tabIndex = -1`), so it takes
95
+ * focus without joining the tab order. Scrolling is suppressed: `revealLine`
96
+ * has already centred the line, and the browser's own focus scroll would
97
+ * fight it. Returns false when there is no current hit to focus.
98
+ */
99
+ export declare function focusCurrentHit(container: HTMLElement): boolean;
100
+ /** Unwrap every mark this module added, restoring the pristine rendered line.
101
+ * `normalize()` re-merges the text nodes `splitText` created so a subsequent
102
+ * scan sees the same node layout it started with. */
103
+ export declare function clearHighlights(container: HTMLElement): void;
104
+ /**
105
+ * Scroll the output so `line` sits mid-viewport.
106
+ *
107
+ * Deliberately not `scrollIntoView` — that walks up and scrolls every scrollable
108
+ * ancestor, which in a docked layout drags the whole panel around. Measuring
109
+ * against the wrapper's own rect keeps the movement inside the output.
110
+ */
111
+ export declare function revealLine(container: HTMLElement, line: HTMLElement): void;
112
+ /**
113
+ * Index of the match nearest the current viewport — where a fresh search should
114
+ * start so the first Enter moves from what the player is looking at rather than
115
+ * from the top of a thousand-line scrollback. Falls back to the last (most
116
+ * recent) match when nothing is on screen.
117
+ */
118
+ export declare function indexNearViewport(container: HTMLElement, matches: OutputMatch[]): number;
119
+ /**
120
+ * Text to prefill the find box with — whatever the player has selected *in the
121
+ * output*, mirroring the browser's own find.
122
+ *
123
+ * Scoped to `container` on purpose. The command line keeps its last command
124
+ * selected after sending, and `window.getSelection()` can surface that, which
125
+ * would seed the box with the command the player just ran.
126
+ */
127
+ export declare function seedFromSelection(container: HTMLElement | null): string;
128
+ /** Locate `target` in a freshly rescanned match list, so incoming MUD output
129
+ * does not move the player's place in the results. */
130
+ export declare function findMatchIndex(matches: OutputMatch[], target: OutputMatch | null): number;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * The query compiler shared by every search bar in the app (the script editor's
3
+ * `ScriptSearch`, the output find bar). Turns a raw query plus the case/regex
4
+ * toggles into something that hands back match ranges for any string.
5
+ */
6
+ /** Half-open `[start, end)` character offsets of one match. */
7
+ export type MatchRange = [number, number];
8
+ export interface SearchMatcher {
9
+ /** False when `useRegex` was set and the pattern failed to compile — the
10
+ * caller shows the input as invalid rather than reporting "no results". */
11
+ valid: boolean;
12
+ ranges: (s: string) => MatchRange[];
13
+ }
14
+ /** Compile a search bar query into a matcher honouring the case / regex flags. */
15
+ export declare function buildMatcher(pattern: string, matchCase: boolean, useRegex: boolean): SearchMatcher;
@@ -0,0 +1,3 @@
1
+ /** Return `value` delayed by `delay` ms — collapses bursts of keystrokes so an
2
+ * expensive scan runs at most once per pause in typing. */
3
+ export declare function useDebounced<T>(value: T, delay: number): T;
@@ -64,5 +64,8 @@ export declare class TtsManager {
64
64
  private advance;
65
65
  private beginUtterance;
66
66
  private setState;
67
+ /** Text of the most recent ttsSpeechStarted, so speaking over a running
68
+ * utterance can tell a genuinely new one from a repeat. */
69
+ private lastStarted;
67
70
  }
68
71
  export {};
@@ -1,7 +1,9 @@
1
1
  /** Reactive MRU command history wrapper around localStorage. History is scoped
2
2
  * to `connectionId` so each profile keeps its own. `saveSize` caps how many
3
- * entries are persisted (Mudlet's `commandLineHistorySaveSize`). */
4
- export declare function useCommandHistory(connectionId: string | null, saveSize?: number): {
3
+ * entries are persisted (Mudlet's `commandLineHistorySaveSize`); `persist`
4
+ * is Mudlet's per-command-line `setSaveCommandHistory`, which stops the writing
5
+ * without touching the history this session already has. */
6
+ export declare function useCommandHistory(connectionId: string | null, saveSize?: number, persist?: boolean): {
5
7
  history: string[];
6
8
  add: (item: string) => void;
7
9
  };
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Connection ids currently open in *another* browser tab.
3
+ *
4
+ * Every open profile holds an exclusive Web Lock named `mudix:profile:<id>`
5
+ * (see profileLock.ts), and `navigator.locks.query()` lists held locks across
6
+ * all same-origin tabs — so the held profile locks are exactly the open set.
7
+ * The connection screen renders with no profile active, so this tab holds no
8
+ * profile lock of its own and every hit belongs to someone else; no
9
+ * self-filtering is needed.
10
+ *
11
+ * Polled, because Web Locks has no change event. The refresh is also driven by
12
+ * focus/visibility so returning to the launcher tab after closing a profile
13
+ * elsewhere updates immediately instead of waiting out the interval.
14
+ *
15
+ * Returns an empty set where the API is unavailable (legacy browser, insecure
16
+ * context, test env) — the same "can't tell, so don't claim anything" fallback
17
+ * the lock itself uses.
18
+ */
19
+ /** The profile ids among a `navigator.locks.query()` held-lock list. Non-profile
20
+ * locks (any other feature using Web Locks) and unnamed entries are ignored. */
21
+ export declare function openProfileIds(held: readonly {
22
+ name?: string;
23
+ }[] | undefined): Set<string>;
24
+ /** Whether two id sets hold the same members — used to skip no-op state updates. */
25
+ export declare const sameIds: (a: ReadonlySet<string>, b: ReadonlySet<string>) => boolean;
26
+ export declare function useOpenProfiles(enabled?: boolean, refreshMs?: number): Set<string>;
@@ -1,4 +1,5 @@
1
1
  import type { MediaCaptionInfo } from '../sound/closedCaption';
2
+ import type { MediaOrigin } from '../sound/SoundManager';
2
3
  type LoaderFn = (path: string) => Promise<ArrayBuffer | null>;
3
4
  export interface PlayVideoOptions {
4
5
  name: string;
@@ -11,6 +12,10 @@ export interface PlayVideoOptions {
11
12
  height?: string;
12
13
  /** Optional closed-caption text (Mudlet's media `caption`). */
13
14
  caption?: string;
15
+ /** Which mute gate governs this playback. Default 'api' — the only way to
16
+ * reach a video today is Lua's `playVideoFile`, but GMCP `Client.Media`
17
+ * defines a video type, so the gate is parameterised like the sounds'. */
18
+ origin?: MediaOrigin;
14
19
  }
15
20
  export declare class VideoManager {
16
21
  private loader;
@@ -20,6 +25,14 @@ export declare class VideoManager {
20
25
  private active;
21
26
  /** Buffers fetched ahead of play via loadVideoFile, keyed by VFS path. */
22
27
  private prefetched;
28
+ /**
29
+ * Per-origin mute gates, the video half of {@link SoundManager}'s. A muted
30
+ * origin's videos keep playing (picture and position both advance) with the
31
+ * element muted, and new ones start muted — nothing is stopped, so unmuting
32
+ * restores audio mid-clip. `volume` is left alone so `getPlayingVideos`
33
+ * still reports what the script asked for.
34
+ */
35
+ private muted;
23
36
  /** Fires when a video ends naturally or is stopped — mirrors Mudlet's
24
37
  * sysMediaFinished. */
25
38
  onEnded: ((name: string, path: string) => void) | null;
@@ -28,6 +41,10 @@ export declare class VideoManager {
28
41
  onMediaCaption: ((info: MediaCaptionInfo) => void) | null;
29
42
  setLoader(fn: LoaderFn | null): void;
30
43
  setMountPoint(fn: (() => HTMLElement | null) | null): void;
44
+ /** Mudlet `muteMediaAPI` / `muteMediaGame`, applied to video playback. See
45
+ * {@link muted}; the sound-side twin is `SoundManager.setOriginMuted`. */
46
+ setOriginMuted(origin: MediaOrigin, muted: boolean): void;
47
+ isOriginMuted(origin: MediaOrigin): boolean;
31
48
  /**
32
49
  * Mudlet `loadVideoFile`. Preloads (fetches + caches) a VFS-backed video so
33
50
  * the first playVideoFile has no fetch latency. http(s)/data/blob URLs need
@@ -1,7 +1,7 @@
1
1
  import { type OutputRendererControls } from '../output/OutputRenderer';
2
2
  import type { Console } from '../../mud/text/Console';
3
3
  import type { AnsiAwareBuffer } from '../../mud/text/FormatState';
4
- import type { DockSide, WindowHandle, WindowOpenOptions, ScriptWindowRenderData } from './types';
4
+ import type { DockSide, MxpTabPage, WindowHandle, WindowOpenOptions, ScriptWindowRenderData } from './types';
5
5
  import { MapStore } from '../../map/MapStore';
6
6
  import { OverlayLayerOrder } from '../layout/overlayLayerOrder';
7
7
  /** Live handle onto a mounted map renderer, registered by MapPanel. Backs the
@@ -103,6 +103,17 @@ export declare class WindowManager {
103
103
  * from openUserWindow panels for windowType() reporting. */
104
104
  private mainViewportEl;
105
105
  private readonly miniConsoles;
106
+ /** MXP `<FRAME>` tab strips, keyed by the frame that hosts the strip. Each
107
+ * page is another frame's console nested inside this one's viewport, so
108
+ * switching tabs just swaps which child is visible. Purely transient — MXP
109
+ * frames are torn down on reconnect and never persisted to a layout hint. */
110
+ private readonly frameTabs;
111
+ /** Consoles backing an MXP `<FRAME>`. They render with a hairline border,
112
+ * the way Mudlet outlines every frame container (TMxpFrameManager's
113
+ * `1px solid #444444`), so a server's panes read as separate panes. */
114
+ private readonly mxpFrameIds;
115
+ /** See getMxpBorders. */
116
+ private mxpBorders;
106
117
  private consoleRegistry;
107
118
  private windowHints;
108
119
  private nextZ;
@@ -146,6 +157,9 @@ export declare class WindowManager {
146
157
  /** Bridge to ScriptingEngine.downloadMap — the map UI's "download map from
147
158
  * game" action (Mudlet dlgMapper's Download button). */
148
159
  onDownloadMap?: () => void;
160
+ /** Bridge to the Lua speedwalk entry point (Mudlet Host::startSpeedWalk),
161
+ * wired by ScriptingEngine. Driven by {@link startSpeedWalk}. */
162
+ onStartSpeedWalk?: (from: number, to: number) => void;
149
163
  /** The MMP map URL from GMCP `Client.Map`, or '' when none announced. */
150
164
  get mmpMapLocation(): string;
151
165
  /** Record the game-announced map URL (Mudlet TMap::setMmpMapLocation). */
@@ -327,6 +341,20 @@ export declare class WindowManager {
327
341
  registerMapCallback(id: string, cb: (roomId: number) => void): void;
328
342
  unregisterMapCallback(id: string): void;
329
343
  centerView(roomId: number): boolean;
344
+ /**
345
+ * Mudlet `T2DMap::initiateSpeedWalk` — start a speedwalk from the player's
346
+ * current room to `targetRoomId`, the room the user double-clicked on the
347
+ * map (or the far side of a double-clicked out-of-area exit).
348
+ *
349
+ * Like Mudlet, an unknown target room is ignored outright, while an unknown
350
+ * *player* room is not: it pathfinds from -1 (Mudlet uses the missing
351
+ * profile entry, 0), finds nothing, and the Lua side reports the mapper's
352
+ * "cannot find a path" message rather than failing silently.
353
+ *
354
+ * Returns false when the gesture didn't start a walk — no such room, or no
355
+ * scripting runtime attached to walk it.
356
+ */
357
+ startSpeedWalk(targetRoomId: number): boolean;
330
358
  registerMapControl(id: string, ctrl: MapControl): void;
331
359
  unregisterMapControl(id: string): void;
332
360
  /** Live zoom of the displayed 2D map, or null when no map panel is mounted.
@@ -342,7 +370,7 @@ export declare class WindowManager {
342
370
  private readonly mapViews;
343
371
  /** View ids start at 1 — Mudlet treats 0 as "no view". */
344
372
  private nextMapViewId;
345
- /** The window id backing a map view. */
373
+ /** The window id backing a map view. Reserved to the client — see types.ts. */
346
374
  private static mapViewWindowId;
347
375
  /**
348
376
  * Mudlet `createMapView([areaID])` — open a secondary map window. Returns
@@ -512,6 +540,38 @@ export declare class WindowManager {
512
540
  loadMapXml(xmlText: string): boolean;
513
541
  markAsMiniConsole(id: string): void;
514
542
  isMiniConsole(id: string): boolean;
543
+ /** Flag a console as an MXP frame so it renders with a frame border. Also
544
+ * hides its scrollbar: Mudlet's frames are `TConsole::SubConsole`s, which
545
+ * start with theirs hidden, and no MXP server ever calls enableScrollBar.
546
+ * Idempotent — the layout re-places a frame on every reflow. */
547
+ markAsMxpFrame(id: string): void;
548
+ /** Install (or replace) an MXP frame's tab strip. An empty `pages` list
549
+ * removes it. Pages other than the active one are hidden, which keeps their
550
+ * panel mounted — the content pool renders every open window regardless of
551
+ * visibility, so a background tab keeps its scrollback. */
552
+ setFrameTabs(id: string, pages: MxpTabPage[], active: string): void;
553
+ /** Switch an MXP frame's visible tab. Ignored for an unknown strip or page. */
554
+ selectFrameTab(id: string, pageId: string): void;
555
+ getFrameTabs(id: string): {
556
+ pages: MxpTabPage[];
557
+ active: string;
558
+ } | undefined;
559
+ /** Pixel insets MXP frames have carved out of the main console. Kept apart
560
+ * from the profile's setBorder* sizes (Mudlet splits them the same way, as
561
+ * Host::mUserBorders vs mMxpBorders) so a server layout never overwrites
562
+ * what a script or the settings modal set — OutputArea adds the two. */
563
+ getMxpBorders(): {
564
+ top: number;
565
+ right: number;
566
+ bottom: number;
567
+ left: number;
568
+ };
569
+ setMxpBorders(borders: {
570
+ top: number;
571
+ right: number;
572
+ bottom: number;
573
+ left: number;
574
+ }): void;
515
575
  getRoomIDbyHash(hash: string): number | undefined;
516
576
  setPosition(id: string, x: number, y: number): void;
517
577
  /** Mudlet setWindow for miniconsole / mapper panels — re-portal the panel
@@ -527,6 +587,24 @@ export declare class WindowManager {
527
587
  * override and lets the inherited font take over. */
528
588
  setFont(id: string, family: string): boolean;
529
589
  getFont(id: string): string | null;
590
+ /** Pin a console's rendered row height, in px. Consoles normally lay rows
591
+ * out at the stylesheet's `line-height`, which is roomier than the type
592
+ * itself; an MXP frame instead has to match Mudlet's row metric
593
+ * (`TTextEdit::mFontHeight` = ascent + descent), because its whole geometry
594
+ * contract is "this many rows fit in this box". Undefined restores the
595
+ * stylesheet default. */
596
+ setLineHeight(id: string, px: number | undefined): boolean;
597
+ /** Pin a console back to the first line. Consoles are scrollbacks and follow
598
+ * the tail, which is wrong for a pane the server rewrites wholesale: such a
599
+ * redraw arrives over several network flushes, so following the tail makes
600
+ * the content visibly slide as the rows land, then snap back on the next
601
+ * clear. No-op before the panel mounts. */
602
+ scrollToTop(id: string): void;
603
+ /** Consoles pinned to their first row rather than following the tail. See
604
+ * scrollToTop; TextPanel reads this live through the renderer's followTail. */
605
+ private readonly topAnchored;
606
+ setTopAnchored(id: string, anchored: boolean): void;
607
+ isTopAnchored(id: string): boolean;
530
608
  /** Mudlet setWindowWrap. 0 (or any non-positive value) clears the override. */
531
609
  setWrap(id: string, wrapAt: number): boolean;
532
610
  getWrap(id: string): number | null;
@@ -544,6 +622,14 @@ export declare class WindowManager {
544
622
  * on a console wrapper. Wheel/keyboard scrolling continues regardless; only
545
623
  * the gutter rendering is affected. Persists across mounts; idempotent. */
546
624
  setScrollBarVisible(id: string, visible: boolean): void;
625
+ /**
626
+ * Mudlet `getScrollBarVisible([window])`. Reads back the *intent* set by
627
+ * enable/disableScrollBar rather than whether a gutter is on screen right
628
+ * now: a console that is hidden still answers for the scroll bar it will
629
+ * have when shown. Only the main window starts with one — a miniconsole is
630
+ * created without, as in Mudlet.
631
+ */
632
+ scrollBarVisible(id: string): boolean;
547
633
  /** Mudlet enable/disableHorizontalScrollBar — toggle a horizontal scrollbar
548
634
  * on a console wrapper. mudix wraps long lines by default so this is rarely
549
635
  * needed; included for parity. */
@@ -552,6 +638,26 @@ export declare class WindowManager {
552
638
  * bottom (wheel/touch/keys cannot scroll back). Mudlet forbids this on the
553
639
  * main window; we follow that policy. Returns false on 'main', true otherwise. */
554
640
  setScrollingEnabled(id: string, enabled: boolean): boolean;
641
+ /**
642
+ * Mudlet `timeStampsEnabled(window)` — whether the console is showing its
643
+ * timestamp column. Null when the window has no renderer registered, which
644
+ * is how the Lua wrapper tells a missing window from one that simply has
645
+ * timestamps off. Pre-mount, a queued preference wins over the default.
646
+ */
647
+ timeStampsEnabled(id: string): boolean | null;
648
+ /**
649
+ * Mudlet `enable/disableTimeStamps(window)`. The renderer owns the column,
650
+ * but a script can ask for it before the panel has mounted — openUserWindow
651
+ * followed immediately by enableTimeStamps — so the preference is queued and
652
+ * applied by registerTextPanel. False when the window doesn't exist at all.
653
+ */
654
+ setTimeStamps(id: string, visible: boolean): boolean;
655
+ /** Timestamp preference asked for before the panel mounted. */
656
+ private readonly pendingTimestamps;
657
+ /** Mudlet `scrollingActive(window)` — whether scrollback is currently
658
+ * allowed. Always true for `'main'`, which setScrollingEnabled refuses to
659
+ * change, and true for any console nobody has called disableScrolling on. */
660
+ isScrollingEnabled(id: string): boolean;
555
661
  /** Buffer-line index of the topmost visible line in `id`'s wrapper. In tail
556
662
  * mode returns the last line number (matching Mudlet's mCursorY behaviour at
557
663
  * the end of the buffer). Returns 0 if the element is unmounted or empty.
@@ -560,6 +666,26 @@ export declare class WindowManager {
560
666
  * is `position: relative` — child offsetTop is relative to *that*, not to the
561
667
  * scroll-container `.output-wrapper`, so the rectangles are the unambiguous
562
668
  * way to relate child position to the scroll viewport. */
669
+ /** Whether `id`'s wrapper is mounted with laid-out lines, i.e. whether
670
+ * {@link getScrollLine}'s measurement means anything. A console whose panel
671
+ * isn't on screen yet measures as 0, which is indistinguishable from being
672
+ * genuinely scrolled to the top. */
673
+ canMeasureScroll(id: string): boolean;
674
+ /**
675
+ * Where `scrollTo` last parked each console, as a buffer line index; absent
676
+ * means tail mode (or that only the user has scrolled it).
677
+ *
678
+ * Mudlet's getScroll/scrollTo are buffer-index operations. mudix measured
679
+ * the DOM instead, which is fine for a console the player is looking at but
680
+ * answers nothing useful for one whose panel has not been laid out yet — and
681
+ * a script that scrolls and reads back in the same breath never gives React
682
+ * a chance to commit in between. So the scripted position is remembered here
683
+ * and the measurement is the fallback, not the source of truth.
684
+ */
685
+ private readonly scriptScrollLine;
686
+ /** Called when the *user* scrolls a console, so a script's remembered
687
+ * position stops overriding what they can see. */
688
+ noteUserScroll(id: string): void;
563
689
  getScrollLine(id: string): number;
564
690
  /** Scroll `id`'s wrapper so `line` (0-indexed) sits at the top. `undefined`
565
691
  * resumes tail mode (scroll-to-bottom); negative values count from the end
@@ -594,6 +720,14 @@ export declare class WindowManager {
594
720
  * translates it through cmdLineQssToScopedCss at render time. Returns
595
721
  * false when the window doesn't exist. */
596
722
  setCmdLineStyleSheet(id: string, qss: string): boolean;
723
+ /**
724
+ * Mudlet setCommandForegroundColor / setCommandBackgroundColor for a named
725
+ * window. Mudlet styles a per-window command line through QSS, so the colour
726
+ * is patched into the same `cmdLineStyleSheet` a script could have set by
727
+ * hand rather than kept as a parallel piece of state. False when the window
728
+ * doesn't exist.
729
+ */
730
+ setCmdLineColor(id: string, property: 'color' | 'background-color', r: number, g: number, b: number, a: number): boolean;
597
731
  /** Bind the Lua callback fired when the user presses Enter in this
598
732
  * window's command line. Pass null to clear. Returns false when the
599
733
  * window doesn't exist. */
@@ -670,6 +804,19 @@ export declare class WindowManager {
670
804
  * behaviour). Returns false when the named window doesn't exist.
671
805
  */
672
806
  setTitle(id: string, title?: string): boolean;
807
+ /** Mudlet `getUserWindowTitle(name)` — the header text, whether it was set
808
+ * or generated. Null when there is no such window. */
809
+ getTitle(id: string): string | null;
810
+ /**
811
+ * The title a window carries when nobody has set one. Mudlet builds it from
812
+ * the profile and the window's own name ("<profile> - <window>"), which is
813
+ * what a player sees on a freshly opened user window; mudix used to fall
814
+ * back to the bare id, so resetting a title lost the profile half of it.
815
+ * The profile name is injected by ScriptingAPI — the manager has no other
816
+ * reason to know it.
817
+ */
818
+ profileName: string;
819
+ defaultTitle(id: string): string;
673
820
  focus(id: string): void;
674
821
  has(id: string): boolean;
675
822
  getElement(id: string): HTMLElement | null;
@@ -1,8 +1,11 @@
1
1
  import type { WindowManager } from '../WindowManager';
2
+ import type { ProfileVFS } from '../../../scripting/vfs/ProfileVFS';
2
3
  interface MapPanelProps {
3
4
  id: string;
4
5
  manager: WindowManager;
5
6
  connectionId: string;
7
+ /** Lets "Load map…" read a .dat/.xml the profile already holds. */
8
+ vfs?: ProfileVFS | null;
6
9
  }
7
- export declare function MapPanel({ id, manager, connectionId }: MapPanelProps): import("react/jsx-runtime").JSX.Element;
10
+ export declare function MapPanel({ id, manager, connectionId, vfs }: MapPanelProps): import("react/jsx-runtime").JSX.Element;
8
11
  export {};
@@ -0,0 +1,18 @@
1
+ import type { ProfileVFS } from '../../../scripting/vfs/ProfileVFS';
2
+ import './PackageExportModal.css';
3
+ export type ExportCategory = 'triggers' | 'aliases' | 'scripts' | 'timers' | 'keys' | 'buttons';
4
+ interface Props {
5
+ connectionId: string;
6
+ vfs: ProfileVFS | null;
7
+ /** Pre-check this item (and its subtree), as Mudlet does when the exporter is
8
+ * opened from a right-click in the editor tree. */
9
+ preselect?: {
10
+ category: ExportCategory;
11
+ id: string;
12
+ } | null;
13
+ onClose: () => void;
14
+ /** Reported back for the editor's log pane. */
15
+ onExported?: (message: string) => void;
16
+ }
17
+ export declare function PackageExportModal({ connectionId, vfs, preselect, onClose, onExported }: Props): import("react/jsx-runtime").JSX.Element;
18
+ export {};
@@ -13,6 +13,8 @@ interface TextPanelProps {
13
13
  scrollBoxes?: ScrollBoxManager;
14
14
  fontSize?: number;
15
15
  fontFamily?: string;
16
+ /** Rendered row height in px (MXP frames). See WindowManager.setLineHeight. */
17
+ lineHeight?: number;
16
18
  wrapAt?: number;
17
19
  wrapIndent?: number;
18
20
  wrapHangingIndent?: number;
@@ -31,5 +33,5 @@ interface TextPanelProps {
31
33
  cmdLineValue?: string;
32
34
  cmdLineValueSeq?: number;
33
35
  }
34
- export declare function TextPanel({ id, title, manager, labels, cmdLines, scrollBoxes, fontSize, fontFamily, wrapAt, wrapIndent, wrapHangingIndent, backgroundColor, backgroundImage, cmdLineEnabled, cmdLineStyleSheet, cmdLineValue, cmdLineValueSeq }: TextPanelProps): import("react/jsx-runtime").JSX.Element;
36
+ export declare function TextPanel({ id, title, manager, labels, cmdLines, scrollBoxes, fontSize, fontFamily, lineHeight, wrapAt, wrapIndent, wrapHangingIndent, backgroundColor, backgroundImage, cmdLineEnabled, cmdLineStyleSheet, cmdLineValue, cmdLineValueSeq }: TextPanelProps): import("react/jsx-runtime").JSX.Element;
35
37
  export {};
@@ -11,6 +11,27 @@ export interface DragState {
11
11
  /** true = insert before target in cross-axis; false = after. */
12
12
  splitBefore?: boolean;
13
13
  }
14
+ /** One page of an MXP `<FRAME>` tab strip. `id` is the frame whose console the
15
+ * tab shows — the host frame itself for the first page, a `DOCK`ed child
16
+ * otherwise. See MxpFrameManager. */
17
+ export interface MxpTabPage {
18
+ id: string;
19
+ title: string;
20
+ }
21
+ /** The dockable map widget — the toolbar's Map button and Lua openMapWidget. */
22
+ export declare const MAP_WIDGET_ID = "sys:map";
23
+ /** The embedded mapper created by Lua createMapper / Geyser.Mapper. */
24
+ export declare const MAPPER_WIDGET_ID = "sys:mapper";
25
+ /** Window id backing secondary map views (Lua createMapView). */
26
+ export declare function mapViewWindowId(viewId: number): string;
27
+ export declare const MAP_VIEW_ID_RE: RegExp;
28
+ /**
29
+ * Saved window hints predate the prefix, so a profile stored the map widget's
30
+ * geometry under the bare `map`. Rename it on load, or every existing profile
31
+ * loses its Map position, size and dock side. Bare `mapper`/`mapViewN` need no
32
+ * migration: neither is ever restored from a hint.
33
+ */
34
+ export declare function migrateClientWindowHints(hints: Record<string, WindowOpenOptions>): Record<string, WindowOpenOptions>;
14
35
  export interface WindowOpenOptions {
15
36
  title?: string;
16
37
  kind?: 'text' | 'html' | 'map';
@@ -46,6 +67,10 @@ export interface WindowOpenOptions {
46
67
  fontSize?: number;
47
68
  /** Output font family override (Mudlet setFont). */
48
69
  fontFamily?: string;
70
+ /** Rendered row height in px, overriding the stylesheet's line-height. Set
71
+ * for MXP frames so a box of N rows really holds N rows. See
72
+ * WindowManager.setLineHeight. */
73
+ lineHeight?: number;
49
74
  /** Character-column wrap width (Mudlet setWindowWrap). 0/undefined disables. */
50
75
  wrapAt?: number;
51
76
  /** Indent (in characters) of newline-started lines (Mudlet setWindowWrapIndent). */
@@ -102,6 +127,8 @@ export interface ScriptWindowRenderData {
102
127
  splitFlex?: number;
103
128
  fontSize?: number;
104
129
  fontFamily?: string;
130
+ /** See WindowOpenOptions.lineHeight. */
131
+ lineHeight?: number;
105
132
  wrapAt?: number;
106
133
  wrapIndent?: number;
107
134
  wrapHangingIndent?: number;
@@ -142,4 +169,12 @@ export interface ScriptWindowRenderData {
142
169
  * still trigger React's seed effect (otherwise printCmdLine('x') after a
143
170
  * user types 'x' would do nothing). */
144
171
  cmdLineValueSeq?: number;
172
+ /** MXP `<FRAME TITLE=…>` tab strip rendered as this window's chrome. Absent
173
+ * for every other window; set only by MxpFrameManager. */
174
+ frameTabs?: {
175
+ pages: MxpTabPage[];
176
+ active: string;
177
+ };
178
+ /** True for a console backing an MXP `<FRAME>` — draws the frame border. */
179
+ isMxpFrame?: boolean;
145
180
  }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Render a caught value for a user-facing error line.
3
+ *
4
+ * `String(err)` is the usual shorthand, but it collapses every non-`Error`
5
+ * object to the literal text "[object Object]" — which is exactly what the
6
+ * script error log showed for errors that never went through Lua's own
7
+ * `error()`. Those come from a JS exception escaping the wasm boundary: the
8
+ * metamethod trampolines wasmoon installs for JS objects (`__index`,
9
+ * `__newindex`, `__tostring`) don't wrap the call, so a throw inside a getter
10
+ * unwinds straight past Lua into whoever called `resume`. Nothing rewrites it
11
+ * into a Lua error string on the way, so the value arrives raw — and a raw
12
+ * plain object rendered with `String` says nothing at all.
13
+ *
14
+ * Errors keep rendering as their bare `message` (unchanged); everything else is
15
+ * described by what it actually is.
16
+ *
17
+ * Pass `label` (the entity or event the failure is attributed to) to also
18
+ * record the live value in devtools when it is opaque — such a value carries no
19
+ * stack of its own, so the object itself is the only handle on where it came
20
+ * from.
21
+ */
22
+ export declare function describeThrown(err: unknown, label?: string): string;
@@ -34,6 +34,11 @@ export interface LocalFontEntry {
34
34
  }
35
35
  export declare function isLocalFontApiSupported(): boolean;
36
36
  export declare function queryLocalFonts(): Promise<LocalFontEntry[]>;
37
+ /** The family consoles actually render in when the profile sets none — the
38
+ * bundled face App.css applies (picked to match Mudlet's default look). Mudlet
39
+ * always reports a concrete family from getFont, never an empty string, and
40
+ * UI_spec checks that what comes back is a real name. */
41
+ export declare const DEFAULT_OUTPUT_FONT_FAMILY = "Bitstream Vera Sans Mono";
37
42
  export declare function getUniversalDefaultFonts(): readonly string[];
38
43
  export declare function getCachedLocalFonts(): string[];
39
44
  export declare function setLocalFontsCache(families: Iterable<string>): void;