weml-monaco 0.9.0 → 0.10.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/CHANGELOG.md CHANGED
@@ -8,6 +8,27 @@ This package shares the language logic of the VS Code extension (`weml-support`)
8
8
  entries below track the port catching up with its provider changes; since the shared language
9
9
  features, both take the same code.
10
10
 
11
+ ## [0.10.0] — 2026-10-14
12
+
13
+ ### Added
14
+ - The preview takes the host's direction and language: `createWemlPreview(editor, { language: { dir,
15
+ lang } })` and `preview.setLanguage(...)`, each field over the document's own `<html dir lang>`. A
16
+ fragment has none — a paragraph of a right-to-left publication rendered left to right.
17
+ - The editor passes them on: `preview.language` (`createWemlEditor`), `setPreviewLanguage`; in
18
+ `<WemlEditor>` the prop is followed, as `preview.theme` is.
19
+
20
+ VS Code has no counterpart: its preview shows a file, whose own `<html dir lang>` it already
21
+ follows, and has no host to know a language the file does not declare.
22
+
23
+ ## [0.9.1] — 2026-10-14
24
+
25
+ ### Fixed
26
+ - `createWemlEditor`: an editor opened after the last one on the same Monaco closed has the language
27
+ again — highlighting, completion, hover, diagnostics, formatting. The `weml` id outlives the
28
+ disposed registration (Monaco cannot unregister a language) and was taken for someone else's.
29
+ - The paragraph mode keeps the host's `readOnly` (`editorOptions`, the Settings view) and its
30
+ `readOnlyMessage`: the wrapper guard only adds its own lock, while the selection touches the wrapper.
31
+
11
32
  ## [0.9.0] — 2026-10-13
12
33
 
13
34
  ### Added
package/README.md CHANGED
@@ -240,7 +240,9 @@ weml.dispose();
240
240
  `.monaco-component`); the workbench colours Monaco lacks (tabs, side bar, status
241
241
  bar) are derived from the editor's. The stylesheet is injected, and published as
242
242
  `weml-monaco/editor.css` (`injectStyles: false` to link it).
243
- - The preview renders with `weml-stylesheet`, so this entry needs it installed.
243
+ - The preview renders with `weml-stylesheet`, so this entry needs it installed. The host's
244
+ direction and language go in `preview.language` (`{ dir, lang }`, over the document's own; a
245
+ paragraph has none), changed with `setPreviewLanguage`.
244
246
 
245
247
  ## React
246
248
 
@@ -253,7 +255,7 @@ import { WemlEditor, type WemlEditorApi } from 'weml-monaco/react';
253
255
  A thin component over `createWemlEditor` (React is an optional peer dependency,
254
256
  `>=18`): the options are its props, `editorRef` gives the editor's API
255
257
  (`WemlEditorApi`). Props marked `@mount` are read once — a change to one needs a
256
- new `key`; `fileName`, `saving`, `saveDisabled` and `preview.theme` follow their props.
258
+ new `key`; `fileName`, `saving`, `saveDisabled`, `preview.theme` and `preview.language` follow their props.
257
259
  `sidePanels` take React content (`content: node | (editor) => node`), rendered into
258
260
  their views through portals — inside the application's context. React and react-dom
259
261
  are optional peer dependencies (`>=18`).
@@ -345,7 +347,10 @@ preview.dispose();
345
347
  It renders at once, then after a pause in typing (`delay`, 300 ms), and at once
346
348
  again when the editor gets another model; an unchanged text is not rendered
347
349
  twice (a whole book is not cheap). A whole document keeps its `dir` / `lang`
348
- (right-to-left books render right to left). Blank text shows `emptyText`;
350
+ (right-to-left books render right to left). A fragment has none of its own — a
351
+ paragraph of an Arabic publication — so the host gives them: `language: { dir:
352
+ 'rtl', lang: 'ar-AE' }`, each field over the document's, changed later with
353
+ `preview.setLanguage(...)`. Blank text shows `emptyText`;
349
354
  `transform(text)` picks what to render (e.g. without a wrapper your editor adds).
350
355
 
351
356
  **Scroll sync**, both ways, as in the VS Code preview (and with the same code,
package/dist/editor.cjs CHANGED
@@ -36778,12 +36778,23 @@ function createWemlPreview(editor, options = {}) {
36778
36778
  content.hidden = blank;
36779
36779
  empty.hidden = !blank;
36780
36780
  };
36781
- const setLanguage = (dir, lang) => {
36781
+ let hostLanguage = options.language;
36782
+ let documentDir;
36783
+ let documentLang;
36784
+ const applyLanguage = () => {
36785
+ const dir = hostLanguage?.dir ?? documentDir;
36786
+ const lang = hostLanguage?.lang ?? documentLang;
36782
36787
  if (dir) content.dir = dir;
36783
36788
  else content.removeAttribute("dir");
36784
36789
  if (lang) content.lang = lang;
36785
36790
  else content.removeAttribute("lang");
36786
36791
  };
36792
+ const setDocumentLanguage = (dir, lang) => {
36793
+ documentDir = dir;
36794
+ documentLang = lang;
36795
+ applyLanguage();
36796
+ };
36797
+ applyLanguage();
36787
36798
  const rendered = () => {
36788
36799
  anchors = void 0;
36789
36800
  if (sync.previewWithEditor) followEditor();
@@ -36811,7 +36822,7 @@ function createWemlPreview(editor, options = {}) {
36811
36822
  generation = update2.generation;
36812
36823
  shownVersion = { model, version };
36813
36824
  showBlank(update2.blank);
36814
- setLanguage(update2.blank ? void 0 : update2.dir, update2.blank ? void 0 : update2.lang);
36825
+ setDocumentLanguage(update2.blank ? void 0 : update2.dir, update2.blank ? void 0 : update2.lang);
36815
36826
  rendered();
36816
36827
  }
36817
36828
  done();
@@ -36831,7 +36842,7 @@ function createWemlPreview(editor, options = {}) {
36831
36842
  const blank = markup.trim() === "";
36832
36843
  showBlank(blank);
36833
36844
  const language = blank ? {} : (0, import_weml_stylesheet.documentLanguage)(markup);
36834
- setLanguage(language.dir, language.lang);
36845
+ setDocumentLanguage(language.dir, language.lang);
36835
36846
  const { body, bodyLine } = splitDocument(markup);
36836
36847
  const next = blank ? [] : splitPreviewChunks(body, firstLine + bodyLine);
36837
36848
  chunks.apply(diffPreviewChunks(texts, next));
@@ -36933,6 +36944,11 @@ function createWemlPreview(editor, options = {}) {
36933
36944
  return {
36934
36945
  element: element2,
36935
36946
  setTheme,
36947
+ setLanguage(language) {
36948
+ hostLanguage = language;
36949
+ applyLanguage();
36950
+ anchors = void 0;
36951
+ },
36936
36952
  refresh: now,
36937
36953
  setScrollSync(next) {
36938
36954
  sync = scrollSyncOf(next);
@@ -38220,10 +38236,13 @@ function injectEditorStyles(doc = document) {
38220
38236
  // src/editor/wrapperGuard.ts
38221
38237
  var HIDDEN_AREAS_SOURCE = "weml-editor.wrapper";
38222
38238
  var WRAPPER_LINE_CLASS = "weml-editor-wrapper-line";
38223
- function guardWrapper(m2, editor, hideWrapper) {
38239
+ var WRAPPER_MESSAGE = { value: "The `<div id>` wrapper is not part of the paragraph and cannot be edited." };
38240
+ function guardWrapper(m2, editor, hideWrapper, hostOptions) {
38224
38241
  const model = editor.getModel();
38225
38242
  if (!model) return { dispose() {
38243
+ }, setHostOptions() {
38226
38244
  } };
38245
+ let host = hostOptions;
38227
38246
  const disposables = [];
38228
38247
  const touchesWrapper = (selection) => selection.startLineNumber < 2 || selection.endLineNumber > model.getLineCount() - 1;
38229
38248
  const clampSelections = () => {
@@ -38240,15 +38259,17 @@ function guardWrapper(m2, editor, hideWrapper) {
38240
38259
  })
38241
38260
  );
38242
38261
  };
38262
+ let applied;
38243
38263
  const syncReadOnly = () => {
38244
- const readOnly = (editor.getSelections() ?? []).some(touchesWrapper);
38245
- if (editor.getOption(m2.editor.EditorOption.readOnly) !== readOnly) editor.updateOptions({ readOnly });
38264
+ const locked = (editor.getSelections() ?? []).some(touchesWrapper);
38265
+ const readOnly = host.readOnly === true || locked;
38266
+ const readOnlyMessage = locked && host.readOnly !== true ? WRAPPER_MESSAGE : host.readOnlyMessage;
38267
+ if (applied?.readOnly === readOnly && applied.readOnlyMessage === readOnlyMessage) return;
38268
+ applied = { readOnly, readOnlyMessage };
38269
+ editor.updateOptions({ readOnly, readOnlyMessage });
38246
38270
  };
38247
38271
  const offset = contentLineOffset("paragraph", hideWrapper);
38248
- editor.updateOptions({
38249
- readOnlyMessage: { value: "The `<div id>` wrapper is not part of the paragraph and cannot be edited." },
38250
- lineNumbers: (lineNumber) => String(lineNumber - offset)
38251
- });
38272
+ editor.updateOptions({ lineNumbers: (lineNumber) => String(lineNumber - offset) });
38252
38273
  disposables.push(
38253
38274
  editor.onDidChangeCursorSelection(() => {
38254
38275
  if (hideWrapper) clampSelections();
@@ -38313,6 +38334,11 @@ function guardWrapper(m2, editor, hideWrapper) {
38313
38334
  })
38314
38335
  );
38315
38336
  return {
38337
+ setHostOptions(options) {
38338
+ host = options;
38339
+ applied = void 0;
38340
+ syncReadOnly();
38341
+ },
38316
38342
  dispose() {
38317
38343
  for (const disposable of disposables) disposable.dispose();
38318
38344
  wrapperLines.clear();
@@ -38352,6 +38378,7 @@ function jsonOptions(options) {
38352
38378
  return Object.fromEntries(Object.entries(options).filter(([key, value]) => !NOT_SETTINGS.has(key) && value !== void 0 && plain(value)));
38353
38379
  }
38354
38380
  var sharedLanguage = /* @__PURE__ */ new WeakMap();
38381
+ var ownLanguageId = /* @__PURE__ */ new WeakSet();
38355
38382
  var editorCount = 0;
38356
38383
  function acquireLanguage(m2, options) {
38357
38384
  if (options.language) return { registration: options.language, release() {
@@ -38361,12 +38388,13 @@ function acquireLanguage(m2, options) {
38361
38388
  shared.users++;
38362
38389
  return { registration: shared.registration, release: () => releaseShared(m2) };
38363
38390
  }
38364
- if (m2.languages.getLanguages().some((language) => language.id === "weml")) {
38391
+ if (!ownLanguageId.has(m2) && m2.languages.getLanguages().some((language) => language.id === "weml")) {
38365
38392
  return { registration: { analysis: localAnalysis, formatter: localFormatter, dispose() {
38366
38393
  } }, release() {
38367
38394
  } };
38368
38395
  }
38369
38396
  const registration = options.workers ? registerWemlWorker(m2, { ...options.languageOptions, worker: options.workers.analysis, formatWorker: options.workers.format }) : registerWeml(m2, options.languageOptions);
38397
+ ownLanguageId.add(m2);
38370
38398
  sharedLanguage.set(m2, { registration, users: 1 });
38371
38399
  return { registration, release: () => releaseShared(m2) };
38372
38400
  }
@@ -38458,7 +38486,12 @@ function createWemlEditor(m2, container, options = {}) {
38458
38486
  const editorHost = element("div", { className: "wb-editor wb-part" });
38459
38487
  const baseOptions = { ...EDITOR_OPTIONS, ...options.editorOptions };
38460
38488
  const editor = m2.editor.create(editorHost, { ...baseOptions, ...layout.editorOptions, model });
38461
- if (mode === "paragraph") disposables.push(guardWrapper(m2, editor, options.hideWrapper ?? false));
38489
+ const wrapperGuard = mode === "paragraph" ? guardWrapper(m2, editor, options.hideWrapper ?? false, { ...baseOptions, ...layout.editorOptions }) : void 0;
38490
+ if (wrapperGuard) disposables.push(wrapperGuard);
38491
+ const applyEditorOptions = (next) => {
38492
+ editor.updateOptions(next);
38493
+ wrapperGuard?.setHostOptions(next);
38494
+ };
38462
38495
  const content = createContentApi(editor, mode, lineOffset, initialValue);
38463
38496
  const valueListeners = /* @__PURE__ */ new Set();
38464
38497
  const problemListeners = /* @__PURE__ */ new Set();
@@ -38732,6 +38765,7 @@ function createWemlEditor(m2, container, options = {}) {
38732
38765
  const views = /* @__PURE__ */ new Map();
38733
38766
  let previewScrollSync = options.preview?.scrollSync !== false;
38734
38767
  let previewTheme = options.preview?.theme;
38768
+ let previewLanguage = options.preview?.language;
38735
38769
  const createView = (view) => {
38736
38770
  const host = element("div");
38737
38771
  if (view === "problems") {
@@ -38763,6 +38797,7 @@ function createWemlEditor(m2, container, options = {}) {
38763
38797
  // Cut and compared in the WEML worker when the language runs there (a file: no transform).
38764
38798
  analysis,
38765
38799
  theme: previewTheme,
38800
+ language: previewLanguage,
38766
38801
  scrollSync: previewScrollSync,
38767
38802
  container: host,
38768
38803
  // A paragraph without its wrapper, from the model's second line (the scroll sync matches
@@ -38814,12 +38849,12 @@ function createWemlEditor(m2, container, options = {}) {
38814
38849
  const own = Object.fromEntries(Object.entries(jsonOptions(parsed)).filter(([key, value]) => JSON.stringify(value) !== JSON.stringify(base[key])));
38815
38850
  if (JSON.stringify(own) === JSON.stringify(layout.editorOptions ?? {})) return;
38816
38851
  layout.editorOptions = own;
38817
- editor.updateOptions({ ...base, ...own });
38852
+ applyEditorOptions({ ...base, ...own });
38818
38853
  saveLayout();
38819
38854
  }
38820
38855
  function resetSettings() {
38821
38856
  layout.editorOptions = {};
38822
- editor.updateOptions(jsonOptions(baseOptions));
38857
+ applyEditorOptions(jsonOptions(baseOptions));
38823
38858
  saveLayout();
38824
38859
  views.get("settings")?.settings?.setText(settingsText());
38825
38860
  }
@@ -39419,6 +39454,10 @@ function createWemlEditor(m2, container, options = {}) {
39419
39454
  previewTheme = theme;
39420
39455
  views.get("preview")?.preview?.setTheme(theme);
39421
39456
  },
39457
+ setPreviewLanguage(language2) {
39458
+ previewLanguage = language2;
39459
+ views.get("preview")?.preview?.setLanguage(language2);
39460
+ },
39422
39461
  addStatusBarItem(item, alignment = "left") {
39423
39462
  item.classList.add("wb-status-item");
39424
39463
  (alignment === "left" ? statusLeft : statusRight).append(item);