composite-monaco-diff 1.0.2

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 (49) hide show
  1. package/LICENSE +338 -0
  2. package/dist/cjs/CenterAndHeightResizer.cjs +325 -0
  3. package/dist/cjs/CenterResizer.cjs +195 -0
  4. package/dist/cjs/Module.cjs +12 -0
  5. package/dist/cjs/MonacoDiffManager.cjs +306 -0
  6. package/dist/cjs/composite-monaco-diff.cjs +123 -0
  7. package/dist/cjs/manager/index.cjs +177 -0
  8. package/dist/cjs/react.cjs +64 -0
  9. package/dist/cjs/trimLeft.cjs +28 -0
  10. package/dist/cjs/urlchange/ChildSection.cjs +174 -0
  11. package/dist/cjs/urlchange/index.cjs +229 -0
  12. package/dist/cjs/urlchange/toolsURLSearchParams.cjs +111 -0
  13. package/dist/cjs/urlchange/urlchange.cjs +197 -0
  14. package/dist/cjs/web-component/from-js/index.cjs +160 -0
  15. package/dist/cjs/web-component/from-scripts/index.cjs +114 -0
  16. package/dist/esm/CenterAndHeightResizer.js +325 -0
  17. package/dist/esm/CenterResizer.js +195 -0
  18. package/dist/esm/Module.js +12 -0
  19. package/dist/esm/MonacoDiffManager.js +306 -0
  20. package/dist/esm/composite-monaco-diff.js +123 -0
  21. package/dist/esm/manager/index.js +177 -0
  22. package/dist/esm/react.js +64 -0
  23. package/dist/esm/trimLeft.js +28 -0
  24. package/dist/esm/urlchange/ChildSection.js +174 -0
  25. package/dist/esm/urlchange/index.js +229 -0
  26. package/dist/esm/urlchange/toolsURLSearchParams.js +111 -0
  27. package/dist/esm/urlchange/urlchange.js +197 -0
  28. package/dist/esm/web-component/from-js/index.js +160 -0
  29. package/dist/esm/web-component/from-scripts/index.js +114 -0
  30. package/dist/types/CenterAndHeightResizer.d.ts +27 -0
  31. package/dist/types/CenterResizer.d.ts +16 -0
  32. package/dist/types/Module.d.ts +11 -0
  33. package/dist/types/MonacoDiffManager.d.ts +62 -0
  34. package/dist/types/composite-monaco-diff.d.ts +48 -0
  35. package/dist/types/manager/index.d.ts +1 -0
  36. package/dist/types/react.d.ts +19 -0
  37. package/dist/types/trimLeft.d.ts +1 -0
  38. package/dist/types/urlchange/ChildSection.d.ts +41 -0
  39. package/dist/types/urlchange/index.d.ts +1 -0
  40. package/dist/types/urlchange/toolsURLSearchParams.d.ts +40 -0
  41. package/dist/types/urlchange/urlchange.d.ts +107 -0
  42. package/dist/types/web-component/from-js/index.d.ts +1 -0
  43. package/dist/types/web-component/from-scripts/index.d.ts +1 -0
  44. package/package.json +44 -0
  45. package/web-component/Module.js +413 -0
  46. package/web-component/MonacoDiffManager.js +306 -0
  47. package/web-component/composite-monaco-diff.js +123 -0
  48. package/web-component/react.js +64 -0
  49. package/web-component/trimLeft.js +28 -0
@@ -0,0 +1,197 @@
1
+ import { cloneSearchParams, compareNormalizedSearchParams, mergeURLSearchParams, syncURLSearchParams, } from "./toolsURLSearchParams.js";
2
+ /** Set of active listeners registered for URL/history updates. */
3
+ const urlChangeListeners = new Set();
4
+ /** Flag indicating whether the window.history methods have been monkey-patched. */
5
+ let historyPatched = false;
6
+ let originalPushState = null;
7
+ let originalReplaceState = null;
8
+ /**
9
+ * Notifies all registered listeners that a URL change has occurred.
10
+ */
11
+ function notifyUrlChange() {
12
+ for (const listener of urlChangeListeners) {
13
+ listener();
14
+ }
15
+ }
16
+ /**
17
+ * Monkey-patches history.pushState and history.replaceState to notify our listeners
18
+ * of programmatic URL changes. Also listens to the window popstate event.
19
+ */
20
+ function ensureHistoryPatched() {
21
+ if (historyPatched || typeof window === "undefined")
22
+ return;
23
+ historyPatched = true;
24
+ originalPushState = history.pushState.bind(history);
25
+ originalReplaceState = history.replaceState.bind(history);
26
+ history.pushState = function (...args) {
27
+ originalPushState(...args);
28
+ notifyUrlChange();
29
+ };
30
+ history.replaceState = function (...args) {
31
+ originalReplaceState(...args);
32
+ notifyUrlChange();
33
+ };
34
+ window.addEventListener("popstate", notifyUrlChange);
35
+ }
36
+ /**
37
+ * Subscribes a listener callback to URL change events (pushState, replaceState, popstate).
38
+ * Returns an unsubscribe function.
39
+ */
40
+ function subscribeUrlChange(listener) {
41
+ ensureHistoryPatched();
42
+ urlChangeListeners.add(listener);
43
+ return () => {
44
+ urlChangeListeners.delete(listener);
45
+ };
46
+ }
47
+ // ─── Core factory ────────────────────────────────────────────────────────────
48
+ /**
49
+ * Internal factory that creates trackers for a specific configuration of URL parameters.
50
+ * Encapsulates the logic of reading, writing, and separating parameter sets.
51
+ */
52
+ function createURLParamTracker(config, keyFn) {
53
+ /** Applies optional keyFn namespace/index mapping to raw config parameter keys. */
54
+ const applyKey = (baseKey, ctx) => (keyFn ? keyFn(baseKey, ctx) : baseKey);
55
+ /**
56
+ * Filters and extracts only the keys governed by this tracker configuration and optional context
57
+ * from the provided search parameters or string.
58
+ */
59
+ function separateIndexedSearchParams(search, ctx) {
60
+ const keys = Object.values(config).map((def) => applyKey(def.getParam, ctx));
61
+ const normalized = typeof search === "string" ? new URLSearchParams(search) : search;
62
+ return mergeURLSearchParams(new URLSearchParams(), keys, normalized);
63
+ }
64
+ /**
65
+ * Reads, decodes, and parses parameter values from search parameters, falling back to defaults.
66
+ */
67
+ function readState(search, ctx) {
68
+ const updatedURLSearchParams = separateIndexedSearchParams(search, ctx);
69
+ const result = {};
70
+ for (const [key, def] of Object.entries(config)) {
71
+ const d = def;
72
+ const raw = updatedURLSearchParams.get(applyKey(d.getParam, ctx));
73
+ result[key] = raw !== null ? d.decode(raw) : d.default;
74
+ }
75
+ return {
76
+ params: result,
77
+ updatedURLSearchParams,
78
+ };
79
+ }
80
+ /** Generates a normalized signature of only the tracked keys for comparison. */
81
+ function trackedSignature(search, ctx) {
82
+ return separateIndexedSearchParams(search, ctx).toString();
83
+ }
84
+ /**
85
+ * Starts tracking the URL, invoking the onChange callback when tracked parameters change.
86
+ * Returns a handle containing functions to read/update the state.
87
+ */
88
+ function trackUrl(onChange, options) {
89
+ const ctx = options?.ctx;
90
+ const replace = options?.replace !== false;
91
+ const fireOnMount = options?.fireOnMount !== false;
92
+ const governedKeys = Object.values(config).map((def) => applyKey(def.getParam, ctx));
93
+ let lastSignature = "";
94
+ /** Decodes the current URL parameters and invokes the onChange callback. */
95
+ const emitCurrentState = (search = window.location.search) => {
96
+ lastSignature = trackedSignature(search, ctx);
97
+ const { params, updatedURLSearchParams } = readState(search, ctx);
98
+ onChange(params, updatedURLSearchParams, governedKeys);
99
+ };
100
+ /** Checks if the signature of the tracked parameters changed, and triggers update if so. */
101
+ const emitIfTrackedChanged = () => {
102
+ const signature = trackedSignature(window.location.search, ctx);
103
+ if (signature === lastSignature)
104
+ return;
105
+ emitCurrentState();
106
+ };
107
+ /** Syncs internal state signature and calls onChange callback after writing updates. */
108
+ const syncAfterLocalWrite = (next) => {
109
+ emitCurrentState(next);
110
+ };
111
+ /** Commits the new URLSearchParams using standard history methods. */
112
+ const writeSearch = (next) => {
113
+ const current = cloneSearchParams(new URLSearchParams(window.location.search));
114
+ if (compareNormalizedSearchParams(next, current))
115
+ return;
116
+ syncAfterLocalWrite(next);
117
+ };
118
+ /** Helper to set/update a single parameter value. */
119
+ const setParam = (key, value) => {
120
+ const def = config[key];
121
+ const finalKey = applyKey(def.getParam, ctx);
122
+ const current = cloneSearchParams(new URLSearchParams(window.location.search));
123
+ const patch = new URLSearchParams();
124
+ if (JSON.stringify(value) !== JSON.stringify(def.default)) {
125
+ patch.set(finalKey, def.encode(value));
126
+ }
127
+ writeSearch(syncURLSearchParams(current, [finalKey], patch));
128
+ };
129
+ /** Helper to set/update multiple parameter values at once. */
130
+ const setParams = (updates) => {
131
+ const current = cloneSearchParams(new URLSearchParams(window.location.search));
132
+ const governed = [];
133
+ const patch = new URLSearchParams();
134
+ for (const [key, value] of Object.entries(updates)) {
135
+ const def = config[key];
136
+ if (def && value !== undefined) {
137
+ const finalKey = applyKey(def.getParam, ctx);
138
+ governed.push(finalKey);
139
+ if (JSON.stringify(value) !== JSON.stringify(def.default)) {
140
+ patch.set(finalKey, def.encode(value));
141
+ }
142
+ }
143
+ }
144
+ writeSearch(syncURLSearchParams(current, governed, patch));
145
+ };
146
+ if (fireOnMount) {
147
+ emitCurrentState();
148
+ }
149
+ else {
150
+ lastSignature = trackedSignature(window.location.search, ctx);
151
+ }
152
+ const unsubscribe = subscribeUrlChange(emitIfTrackedChanged);
153
+ return {
154
+ setParam,
155
+ setParams,
156
+ getParams: () => readState(window.location.search, ctx).params,
157
+ getUpdatedURLSearchParams: () => separateIndexedSearchParams(window.location.search, ctx),
158
+ refresh: () => emitCurrentState(),
159
+ disconnect: unsubscribe,
160
+ governedKeys,
161
+ };
162
+ }
163
+ return {
164
+ separateIndexedSearchParams,
165
+ trackUrl,
166
+ readState,
167
+ };
168
+ }
169
+ /**
170
+ * Vanilla counterpart to React `modURLSearchParams`. Define params once, then call
171
+ * `trackUrl(onChange)` or use the standalone `trackUrl(config, onChange)` helper.
172
+ */
173
+ export default function modURLSearchParams(config, keyFn) {
174
+ return createURLParamTracker(config, keyFn);
175
+ }
176
+ /**
177
+ * Subscribe to URL changes for a fixed param schema. The callback runs only when
178
+ * tracked keys (per config) change — other query/hash navigation is ignored.
179
+ *
180
+ * @example
181
+ * const { setParam, setParams } = trackUrl(
182
+ * {
183
+ * emptyList: { default: false, getParam: "emp", encode: (v) => (v ? "1" : "0"), decode: (v) => v === "1" },
184
+ * },
185
+ * (params, updatedURLSearchParams) => { ... },
186
+ * );
187
+ */
188
+ export function trackUrl(config, onChange, options) {
189
+ const { keyFn, ...trackOptions } = options ?? {};
190
+ const tracker = createURLParamTracker(config, keyFn);
191
+ return tracker.trackUrl(onChange, trackOptions);
192
+ }
193
+ /** Subscribe to query-string updates (history push/replace/popstate). */
194
+ export function onUrlChange(listener) {
195
+ return subscribeUrlChange(listener);
196
+ }
197
+ export { mergeURLSearchParams, createURLParamTracker, compareNormalizedSearchParams };
@@ -0,0 +1,160 @@
1
+ import modURLSearchParams from "../../urlchange/urlchange.js";
2
+ import { syncURLSearchParams, buildUrlWithSearchParams } from "../../urlchange/toolsURLSearchParams.js";
3
+ import { MonacoDiffManager } from "../../MonacoDiffManager.js";
4
+ import { isMonacoTheme, MonacoDiffElement } from "../../composite-monaco-diff.js";
5
+ import { CenterAndHeightResizer } from "../../CenterAndHeightResizer.js";
6
+ await customElements.whenDefined(MonacoDiffElement.tagName);
7
+ await customElements.whenDefined(CenterAndHeightResizer.tagName);
8
+ const diffEl = document.querySelector(MonacoDiffElement.tagName);
9
+ if (!(diffEl instanceof MonacoDiffElement)) {
10
+ throw new Error("Missing <composite-monaco-diff> element");
11
+ }
12
+ await diffEl.whenReady();
13
+ let model;
14
+ let mgr;
15
+ const original = `
16
+ const loadMonaco = (vsPath = VS_PATH) =>
17
+ new Promise((resolve, reject) => {
18
+ const win = window;
19
+
20
+ const finish = () => {
21
+ win.require.config({ paths: { vs: vsPath } });
22
+ win.require(["vs/editor/editor.main"], () => resolve(win.monaco));
23
+ };
24
+
25
+ if (win.require && win.monaco) return resolve(win.monaco);
26
+ if (win.require) return finish();
27
+
28
+ const script = document.createElement("script");
29
+ script.src = \`\${vsPath}/loader.js\`;
30
+ script.async = true;
31
+ script.onload = () => finish();
32
+ script.onerror = () => reject(new Error(\`Failed to load Monaco loader from \${vsPath}\`));
33
+ document.head.appendChild(script);
34
+ });
35
+ `;
36
+ const modified = `
37
+ const loadMonaco = (vsPath = VS_PATH) =>
38
+ new Promise((resolve, reject) => {
39
+ const win = window;
40
+
41
+ const finish = () => {
42
+ win.require.config({ paths: { vs: vsPath } });
43
+ win.require(["vs/editor/editor.main"], () => resolve(win.monaco));
44
+ };
45
+
46
+ if (win.require && win.moneco) return resolve(win.monaco);
47
+
48
+ const script = document.createElement("script");
49
+ script.src = \`\${vsPath}/loader.js\`;
50
+ script.async = true;
51
+ script.onload = () => finish();
52
+ script.added = 'stuff'
53
+ script.onerror = () => reject(new Error(\`Failed to load Monaco loader from \${vsPath}\`));
54
+ document.head.appendChild(script);
55
+ });
56
+ `;
57
+ function wireResizerUrlSync(resizer, index) {
58
+ const config = {
59
+ left: {
60
+ default: resizer.getAttribute("left") ?? "100px",
61
+ getParam: "l",
62
+ encode: (value) => value,
63
+ decode: (value) => value,
64
+ },
65
+ center: {
66
+ default: resizer.getAttribute("center") ?? "1200px",
67
+ getParam: "c",
68
+ encode: (value) => value,
69
+ decode: (value) => value,
70
+ },
71
+ height: {
72
+ default: resizer.getAttribute("height") ?? "100px",
73
+ getParam: "h",
74
+ encode: (value) => value,
75
+ decode: (value) => value,
76
+ },
77
+ };
78
+ const { trackUrl } = modURLSearchParams(config, (key, i) => `${key}-${i}`);
79
+ const { setParams } = trackUrl((params, updatedURLSearchParams, governedKeys) => {
80
+ resizer.setAttribute("left", params.left);
81
+ resizer.setAttribute("center", params.center);
82
+ resizer.setAttribute("height", params.height);
83
+ const current = new URLSearchParams(window.location.search);
84
+ const next = syncURLSearchParams(current, governedKeys, updatedURLSearchParams);
85
+ if (next.toString() !== current.toString()) {
86
+ const url = buildUrlWithSearchParams(window.location.href, next);
87
+ history.replaceState(history.state, "", url);
88
+ }
89
+ }, { ctx: index, fireOnMount: true });
90
+ const syncToUrl = () => {
91
+ setParams({
92
+ left: resizer.getAttribute("left") ?? config.left.default,
93
+ center: resizer.getAttribute("center") ?? config.center.default,
94
+ height: resizer.getAttribute("height") ?? config.height.default,
95
+ });
96
+ };
97
+ resizer.addEventListener("onLeft", syncToUrl);
98
+ resizer.addEventListener("onCenter", syncToUrl);
99
+ resizer.addEventListener("onHeight", syncToUrl);
100
+ }
101
+ function applyThemeAttribute(mgr, theme) {
102
+ mgr.getMonaco()?.editor.setTheme(theme || "vs");
103
+ }
104
+ function applyLanguageAttribute(mgr, language) {
105
+ mgr.setLanguage(language || undefined);
106
+ }
107
+ document.querySelectorAll(CenterAndHeightResizer.tagName).forEach((el, index) => {
108
+ wireResizerUrlSync(el, index);
109
+ });
110
+ const themeSelect = document.getElementById("theme-select");
111
+ if (!(themeSelect instanceof HTMLSelectElement)) {
112
+ throw new Error("Missing #theme-select element");
113
+ }
114
+ const languageSelect = document.getElementById("language-select");
115
+ if (!(languageSelect instanceof HTMLSelectElement)) {
116
+ throw new Error("Missing #language-select element");
117
+ }
118
+ const { trackUrl: trackUrlNoIndex } = modURLSearchParams({
119
+ theme: {
120
+ default: "",
121
+ getParam: "theme",
122
+ encode: (value) => value,
123
+ decode: (value) => (isMonacoTheme(value) ? value : ""),
124
+ },
125
+ language: {
126
+ default: "javascript",
127
+ getParam: "lang",
128
+ encode: (value) => value,
129
+ decode: (value) => value,
130
+ },
131
+ });
132
+ const { setParam } = trackUrlNoIndex((params, updatedURLSearchParams, governedKeys) => {
133
+ themeSelect.value = params.theme;
134
+ applyThemeAttribute(mgr, params.theme);
135
+ languageSelect.value = params.language;
136
+ applyLanguageAttribute(mgr, params.language);
137
+ const current = new URLSearchParams(window.location.search);
138
+ const next = syncURLSearchParams(current, governedKeys, updatedURLSearchParams);
139
+ if (next.toString() !== current.toString()) {
140
+ const url = buildUrlWithSearchParams(window.location.href, next);
141
+ history.replaceState(history.state, "", url);
142
+ }
143
+ }, { fireOnMount: true });
144
+ themeSelect.addEventListener("change", () => {
145
+ setParam("theme", themeSelect.value);
146
+ });
147
+ languageSelect.addEventListener("change", () => {
148
+ setParam("language", languageSelect.value);
149
+ });
150
+ mgr = diffEl.getManager();
151
+ const editor = mgr.getEditor();
152
+ if (!editor) {
153
+ throw new Error("Diff editor not available");
154
+ }
155
+ model = editor.getModel();
156
+ if (!model) {
157
+ throw new Error("Diff editor has no model");
158
+ }
159
+ model.original.setValue(original);
160
+ model.modified.setValue(modified);
@@ -0,0 +1,114 @@
1
+ import modURLSearchParams from "../../urlchange/urlchange.js";
2
+ import { syncURLSearchParams, buildUrlWithSearchParams } from "../../urlchange/toolsURLSearchParams.js";
3
+ import { isMonacoTheme, MonacoDiffElement } from "../../composite-monaco-diff.js";
4
+ import { CenterAndHeightResizer } from "../../CenterAndHeightResizer.js";
5
+ await customElements.whenDefined(MonacoDiffElement.tagName);
6
+ await customElements.whenDefined(CenterAndHeightResizer.tagName);
7
+ const diffEl = document.querySelector(MonacoDiffElement.tagName);
8
+ if (!(diffEl instanceof MonacoDiffElement)) {
9
+ throw new Error("Missing <composite-monaco-diff> element");
10
+ }
11
+ await diffEl.whenReady();
12
+ function wireResizerUrlSync(resizer, index) {
13
+ const config = {
14
+ left: {
15
+ default: resizer.getAttribute("left") ?? "100px",
16
+ getParam: "l",
17
+ encode: (value) => value,
18
+ decode: (value) => value,
19
+ },
20
+ center: {
21
+ default: resizer.getAttribute("center") ?? "1200px",
22
+ getParam: "c",
23
+ encode: (value) => value,
24
+ decode: (value) => value,
25
+ },
26
+ height: {
27
+ default: resizer.getAttribute("height") ?? "100px",
28
+ getParam: "h",
29
+ encode: (value) => value,
30
+ decode: (value) => value,
31
+ },
32
+ };
33
+ const { trackUrl } = modURLSearchParams(config, (key, i) => `${key}-${i}`);
34
+ const { setParams } = trackUrl((params, updatedURLSearchParams, governedKeys) => {
35
+ resizer.setAttribute("left", params.left);
36
+ resizer.setAttribute("center", params.center);
37
+ resizer.setAttribute("height", params.height);
38
+ const current = new URLSearchParams(window.location.search);
39
+ const next = syncURLSearchParams(current, governedKeys, updatedURLSearchParams);
40
+ if (next.toString() !== current.toString()) {
41
+ const url = buildUrlWithSearchParams(window.location.href, next);
42
+ history.replaceState(history.state, "", url);
43
+ }
44
+ }, { ctx: index, fireOnMount: true });
45
+ const syncToUrl = () => {
46
+ setParams({
47
+ left: resizer.getAttribute("left") ?? config.left.default,
48
+ center: resizer.getAttribute("center") ?? config.center.default,
49
+ height: resizer.getAttribute("height") ?? config.height.default,
50
+ });
51
+ };
52
+ resizer.addEventListener("onLeft", syncToUrl);
53
+ resizer.addEventListener("onCenter", syncToUrl);
54
+ resizer.addEventListener("onHeight", syncToUrl);
55
+ }
56
+ function applyThemeAttribute(diffEl, theme) {
57
+ if (theme) {
58
+ diffEl.setAttribute("theme", theme);
59
+ }
60
+ else {
61
+ diffEl.removeAttribute("theme");
62
+ }
63
+ }
64
+ function applyLanguageAttribute(diffEl, language) {
65
+ if (language) {
66
+ diffEl.setAttribute("language", language);
67
+ }
68
+ else {
69
+ diffEl.removeAttribute("language");
70
+ }
71
+ }
72
+ document.querySelectorAll(CenterAndHeightResizer.tagName).forEach((el, index) => {
73
+ wireResizerUrlSync(el, index);
74
+ });
75
+ const themeSelect = document.getElementById("theme-select");
76
+ if (!(themeSelect instanceof HTMLSelectElement)) {
77
+ throw new Error("Missing #theme-select element");
78
+ }
79
+ const languageSelect = document.getElementById("language-select");
80
+ if (!(languageSelect instanceof HTMLSelectElement)) {
81
+ throw new Error("Missing #language-select element");
82
+ }
83
+ const { trackUrl: trackUrlNoIndex } = modURLSearchParams({
84
+ theme: {
85
+ default: "",
86
+ getParam: "theme",
87
+ encode: (value) => value,
88
+ decode: (value) => (isMonacoTheme(value) ? value : ""),
89
+ },
90
+ language: {
91
+ default: "javascript",
92
+ getParam: "lang",
93
+ encode: (value) => value,
94
+ decode: (value) => value,
95
+ },
96
+ });
97
+ const { setParam } = trackUrlNoIndex((params, updatedURLSearchParams, governedKeys) => {
98
+ themeSelect.value = params.theme;
99
+ applyThemeAttribute(diffEl, params.theme);
100
+ languageSelect.value = params.language;
101
+ applyLanguageAttribute(diffEl, params.language);
102
+ const current = new URLSearchParams(window.location.search);
103
+ const next = syncURLSearchParams(current, governedKeys, updatedURLSearchParams);
104
+ if (next.toString() !== current.toString()) {
105
+ const url = buildUrlWithSearchParams(window.location.href, next);
106
+ history.replaceState(history.state, "", url);
107
+ }
108
+ }, { fireOnMount: true });
109
+ themeSelect.addEventListener("change", () => {
110
+ setParam("theme", themeSelect.value);
111
+ });
112
+ languageSelect.addEventListener("change", () => {
113
+ setParam("language", languageSelect.value);
114
+ });
@@ -0,0 +1,27 @@
1
+ export declare class CenterAndHeightResizer extends HTMLElement {
2
+ static readonly tagName: "center-and-height-resizer";
3
+ leftDiv: HTMLElement;
4
+ rightDiv: HTMLElement;
5
+ centerDiv: HTMLElement;
6
+ resizerLeft: HTMLElement;
7
+ resizerRight: HTMLElement;
8
+ resizerBottom: HTMLElement;
9
+ private _isReady;
10
+ private readonly _readyPromise;
11
+ private _resolveReady;
12
+ constructor();
13
+ static get observedAttributes(): string[];
14
+ attributeChangedCallback(name: string, oldValue: string, newValue: string): void;
15
+ connectedCallback(): void;
16
+ /** Resolves after the first `connectedCallback` (attributes applied to the layout panel). */
17
+ whenReady(): Promise<void>;
18
+ static whenLayoutReady(el: HTMLElement, maxFrames?: number): Promise<void>;
19
+ /** Wait for custom element upgrade, `connectedCallback`, and a non-zero layout panel. */
20
+ static whenHostReady(container: HTMLElement): Promise<HTMLElement>;
21
+ _applyInternalStylesToCenterDiv(): void;
22
+ _initForwarding(): void;
23
+ _syncAttributes(): void;
24
+ getContentRoot(): HTMLElement;
25
+ setupResizer(handle: HTMLElement, target: HTMLElement, attrName: string, invert: boolean): void;
26
+ setupHeightResizer(handle: HTMLElement, target: HTMLElement): void;
27
+ }
@@ -0,0 +1,16 @@
1
+ export declare class CenterResizer extends HTMLElement {
2
+ leftDiv: HTMLElement;
3
+ centerDiv: HTMLElement;
4
+ rightDiv: HTMLElement;
5
+ resizerLeft: HTMLElement;
6
+ resizerRight: HTMLElement;
7
+ constructor();
8
+ static get observedAttributes(): string[];
9
+ attributeChangedCallback(name: string, oldValue: string, newValue: string): void;
10
+ connectedCallback(): void;
11
+ _applyInternalStylesToCenterDiv(): void;
12
+ _initForwarding(): void;
13
+ _syncAttributes(): void;
14
+ getContentRoot(): HTMLElement;
15
+ setupResizer(handle: HTMLElement, target: HTMLElement, attributeName: "left" | "center"): void;
16
+ }
@@ -0,0 +1,11 @@
1
+ /** @es.ts
2
+ {
3
+ mode: "bundle",
4
+ extension: ".js",
5
+ options: {
6
+ }
7
+ }
8
+ @es.ts */
9
+ export * from "./composite-monaco-diff.js";
10
+ export * from "./MonacoDiffManager.js";
11
+ export * from "./trimLeft.js";
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Monaco diff editor manager. Refresh version/CDN URLs: pnpm run monaco -- --skip
3
+ */
4
+ import type * as Monaco from "monaco-editor";
5
+ export declare const MONACO_GENERATED: {
6
+ readonly version: "0.55.1";
7
+ readonly vs: readonly ["https://cdn.jsdelivr.net/npm/monaco-editor@0.55.1/min/vs", "https://unpkg.com/monaco-editor@0.55.1/min/vs", "/monaco/vs"];
8
+ };
9
+ export type MonacoGenerated = typeof MONACO_GENERATED;
10
+ /**
11
+ * Loads Monaco once and keeps it in memory. Tries each configured URL until one works,
12
+ * so the app still runs if a CDN is down.
13
+ */
14
+ export declare function hydrateCache(generated?: MonacoGenerated): Promise<typeof Monaco>;
15
+ export declare const SCRIPT_TYPE_ORIGINAL: "text/original";
16
+ export declare const SCRIPT_TYPE_MODIFIED: "text/modified";
17
+ export interface DeclarativeDiffContent {
18
+ original: string;
19
+ modified: string;
20
+ originalLanguage?: string;
21
+ modifiedLanguage?: string;
22
+ }
23
+ /**
24
+ * Reads the “before” and “after” code from `<script>` tags inside the element (HTML-first setup).
25
+ * Returns nothing if there are no scripts; throws if the markup is incomplete or wrong.
26
+ */
27
+ export declare function readDeclarativeDiffScripts(host: HTMLElement): DeclarativeDiffContent | null;
28
+ export interface MonacoDiffManagerOptions {
29
+ /** When set, reads optional declarative `<script>` children before creating models. */
30
+ host?: HTMLElement;
31
+ original?: string;
32
+ modified?: string;
33
+ /** Applies to both sides when `originalLanguage` / `modifiedLanguage` are not set. */
34
+ language?: string;
35
+ originalLanguage?: string;
36
+ modifiedLanguage?: string;
37
+ editorOptions?: Monaco.editor.IStandaloneDiffEditorConstructionOptions;
38
+ }
39
+ export declare class MonacoDiffManager {
40
+ private _readyPromise;
41
+ private _editor;
42
+ private _resizeObserver;
43
+ private _layoutRaf;
44
+ protected _container: HTMLElement;
45
+ /**
46
+ * Mounts a side-by-side diff editor into the given element: loads Monaco, applies styles,
47
+ * fills in original/modified text, and starts watching size changes.
48
+ */
49
+ constructor(container: HTMLElement, options: MonacoDiffManagerOptions);
50
+ /** Promise that resolves when the editor has finished loading and is safe to use. */
51
+ whenReady(): Promise<void>;
52
+ /** Returns the underlying Monaco diff editor instance (or null if not ready yet). */
53
+ getEditor(): Monaco.editor.IStandaloneDiffEditor | null;
54
+ /** Returns the global Monaco API instance (or null if not loaded yet). */
55
+ getMonaco(): typeof Monaco | null;
56
+ /** Tears down the editor, frees memory, and stops listening for resize events. */
57
+ destroy(): void;
58
+ /** Updates the language for both sides of the diff editor. Falls back to the default language when undefined. */
59
+ setLanguage(language: string | undefined): void;
60
+ /** Resizes the editor to match its container on the next animation frame (avoids jank). */
61
+ private _scheduleLayout;
62
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * `<composite-monaco-diff>` — declarative diff editor with optional embedded `<script>` sources.
3
+ *
4
+ * Import this module to register the element (`customElements.define` at file bottom).
5
+ * Before querying the DOM or calling instance APIs from other modules, wait until the
6
+ * browser knows the tag (same pattern as other custom elements in this repo):
7
+ *
8
+ * import { MonacoDiffElement } from "./composite-monaco-diff.js";
9
+ *
10
+ * await customElements.whenDefined(MonacoDiffElement.tagName);
11
+ *
12
+ * const diff = document.querySelector(MonacoDiffElement.tagName);
13
+ * if (!(diff instanceof MonacoDiffElement)) throw new Error("Missing <composite-monaco-diff>");
14
+ * await diff.whenReady();
15
+ *
16
+ * @example HTML
17
+ * ```html
18
+ * <composite-monaco-diff theme="vs-dark">
19
+ * <script type="text/original" lang="javascript">
20
+ * const a = 1;
21
+ * </script>
22
+ * <script type="text/modified" lang="typescript">
23
+ * const a = 2;
24
+ * </script>
25
+ * </composite-monaco-diff>
26
+ * ```
27
+ */
28
+ import { MonacoDiffManager } from "./MonacoDiffManager.js";
29
+ /** Custom element tag name (`"composite-monaco-diff"`). Pass to `customElements.whenDefined(tagName)`. */
30
+ export declare const tagName: "composite-monaco-diff";
31
+ export declare const MONACO_THEMES: readonly ["vs", "vs-dark", "hc-black", "hc-light"];
32
+ export type MonacoTheme = (typeof MONACO_THEMES)[number];
33
+ export declare function isMonacoTheme(value: string): value is MonacoTheme;
34
+ export declare class MonacoDiffElement extends HTMLElement {
35
+ static readonly tagName: "composite-monaco-diff";
36
+ static get observedAttributes(): string[];
37
+ private _container;
38
+ private _manager;
39
+ constructor();
40
+ connectedCallback(): void;
41
+ attributeChangedCallback(name: string, _oldValue: string | null, newValue: string | null): void;
42
+ disconnectedCallback(): void;
43
+ /** Promise that resolves when the editor has finished loading and is safe to use. */
44
+ whenReady(): Promise<void>;
45
+ getManager(): MonacoDiffManager;
46
+ private _applyTheme;
47
+ private _applyLanguage;
48
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,19 @@
1
+ import * as React from "react";
2
+ import "./composite-monaco-diff.js";
3
+ import type { MonacoTheme } from "./composite-monaco-diff.js";
4
+ export type MonacoDiffProps = {
5
+ className?: string;
6
+ style?: React.CSSProperties;
7
+ id?: string;
8
+ key?: React.Key;
9
+ ref?: React.Ref<HTMLElement>;
10
+ theme?: MonacoTheme;
11
+ language?: string;
12
+ original?: string;
13
+ modified?: string;
14
+ originalLanguage?: string;
15
+ modifiedLanguage?: string;
16
+ children?: React.ReactNode;
17
+ };
18
+ export declare const MonacoDiff: (props: MonacoDiffProps & React.RefAttributes<HTMLElement>) => React.ReactElement;
19
+ export default MonacoDiff;
@@ -0,0 +1 @@
1
+ export default function trimLeft(str: string, offset?: number | string): string;