tosijs-ui 1.6.19 → 1.6.20

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
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.6.20
4
+
5
+ ### Fixed
6
+
7
+ - **`import 'tosijs-ui'` no longer resolves to the IIFE under browser bundlers.**
8
+ The `"."` export had a `"browser"` condition pointing at `dist/iife.js` — the
9
+ self-contained CDN/doc-site bundle that inlines tosijs + marked. Browser-targeted
10
+ bundlers (Vite, webpack, esbuild) picked it, so consumers **double-bundled tosijs**
11
+ instead of externalizing the peer, and named imports broke (the IIFE isn't an ESM
12
+ module). Removed the condition: every `import` now resolves to `dist/index.js`
13
+ (ESM, peers externalized). The IIFE stays for CDN `<script>` use and the naive
14
+ doc site — referenced by file path, never via `import 'tosijs-ui'`.
15
+
16
+ ### Changed
17
+
18
+ - **tosijs peer + dev dependency bumped to `^1.6.6`** — picks up a subtle
19
+ component-lifecycle fix.
20
+
3
21
  ## 1.6.19
4
22
 
5
23
  Doc-system tooling; no component API changes.
@@ -0,0 +1,30 @@
1
+ import { EditorView } from '@codemirror/view';
2
+ import { Extension } from '@codemirror/state';
3
+ /** Map a `<tosi-code mode>` value to a CodeMirror language extension. */
4
+ export declare function languageForMode(mode: string): Extension;
5
+ export interface CmHandle {
6
+ view: EditorView;
7
+ getValue(): string;
8
+ setValue(text: string): void;
9
+ setMode(mode: string): void;
10
+ setReadOnly(readOnly: boolean): void;
11
+ undo(): void;
12
+ redo(): void;
13
+ canUndo(): boolean;
14
+ canRedo(): boolean;
15
+ focus(): void;
16
+ refresh(): void;
17
+ destroy(): void;
18
+ }
19
+ export interface CmOptions {
20
+ value?: string;
21
+ mode?: string;
22
+ readOnly?: boolean;
23
+ /** additional language-adjacent extensions (e.g. a tjs language + autocomplete) */
24
+ languageExtension?: Extension;
25
+ onChange?: (value: string) => void;
26
+ /** style-injection root — pass the host's ShadowRoot when mounted in shadow DOM */
27
+ root?: Document | ShadowRoot;
28
+ }
29
+ /** Create a CodeMirror editor mounted in `parent`, returning an editor-agnostic handle. */
30
+ export declare function createCmEditor(parent: HTMLElement, opts?: CmOptions): CmHandle;
@@ -0,0 +1,132 @@
1
+ /*
2
+ CodeMirror 6 editor factory — the "heavy" module.
3
+
4
+ `code-editor.ts` imports this **dynamically** (on first `<tosi-code>` connect), so
5
+ CodeMirror only enters a consumer's bundle as a separate lazy chunk when the editor
6
+ is actually used — mirroring the way the old ACE build loaded from a CDN on first
7
+ use. No `<tosi-code>` in the page → no CodeMirror.
8
+
9
+ Languages here are the always-available set (js/ts/css/html/markdown). First-class
10
+ tjs (highlighting + runtime-value autocomplete via tjs-lang's editors/codemirror) is
11
+ layered in separately and only when tjs-lang is installed.
12
+ */
13
+ import { EditorView, keymap, lineNumbers, highlightActiveLineGutter, highlightSpecialChars, drawSelection, dropCursor, rectangularSelection, crosshairCursor, highlightActiveLine } from '@codemirror/view';
14
+ import { EditorState, Compartment } from '@codemirror/state';
15
+ import { indentWithTab, history, defaultKeymap, historyKeymap, undo, redo, undoDepth, redoDepth } from '@codemirror/commands';
16
+ import { indentUnit, foldGutter, foldKeymap, indentOnInput, syntaxHighlighting, defaultHighlightStyle, bracketMatching } from '@codemirror/language';
17
+ import { closeBrackets, closeBracketsKeymap, autocompletion, completionKeymap } from '@codemirror/autocomplete';
18
+ import { searchKeymap, highlightSelectionMatches } from '@codemirror/search';
19
+ import { lintKeymap } from '@codemirror/lint';
20
+ import { javascript } from '@codemirror/lang-javascript';
21
+ import { css } from '@codemirror/lang-css';
22
+ import { html } from '@codemirror/lang-html';
23
+ import { markdown } from '@codemirror/lang-markdown';
24
+ /** Map a `<tosi-code mode>` value to a CodeMirror language extension. */
25
+ export function languageForMode(mode) {
26
+ switch (mode) {
27
+ case 'js':
28
+ case 'javascript':
29
+ return javascript();
30
+ case 'ts':
31
+ case 'typescript':
32
+ // tjs highlights fine as TypeScript until the tjs-lang language is wired
33
+ // (workstream B); this keeps `mode="tjs"` readable today.
34
+ case 'tjs':
35
+ case 'ajs':
36
+ return javascript({ typescript: true });
37
+ case 'css':
38
+ return css();
39
+ case 'html':
40
+ return html();
41
+ case 'md':
42
+ case 'markdown':
43
+ return markdown();
44
+ default:
45
+ return javascript();
46
+ }
47
+ }
48
+ // A basicSetup-equivalent without a hard-coded color theme, so the host page's CSS
49
+ // (and dark mode) controls the palette. Line-wrapping mirrors the old ACE `wrap:true`.
50
+ function baseExtensions() {
51
+ return [
52
+ lineNumbers(),
53
+ highlightActiveLineGutter(),
54
+ highlightSpecialChars(),
55
+ history(),
56
+ foldGutter(),
57
+ drawSelection(),
58
+ dropCursor(),
59
+ EditorState.allowMultipleSelections.of(true),
60
+ indentOnInput(),
61
+ indentUnit.of(' '),
62
+ EditorState.tabSize.of(2),
63
+ syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
64
+ bracketMatching(),
65
+ closeBrackets(),
66
+ autocompletion(),
67
+ rectangularSelection(),
68
+ crosshairCursor(),
69
+ highlightActiveLine(),
70
+ highlightSelectionMatches(),
71
+ EditorView.lineWrapping,
72
+ keymap.of([
73
+ indentWithTab,
74
+ ...closeBracketsKeymap,
75
+ ...defaultKeymap,
76
+ ...searchKeymap,
77
+ ...historyKeymap,
78
+ ...foldKeymap,
79
+ ...completionKeymap,
80
+ ...lintKeymap,
81
+ ]),
82
+ ];
83
+ }
84
+ /** Create a CodeMirror editor mounted in `parent`, returning an editor-agnostic handle. */
85
+ export function createCmEditor(parent, opts = {}) {
86
+ const language = new Compartment();
87
+ const readonly = new Compartment();
88
+ const langExt = opts.languageExtension ?? languageForMode(opts.mode ?? 'javascript');
89
+ const view = new EditorView({
90
+ parent,
91
+ root: opts.root,
92
+ state: EditorState.create({
93
+ doc: opts.value ?? '',
94
+ extensions: [
95
+ baseExtensions(),
96
+ language.of(langExt),
97
+ readonly.of(EditorState.readOnly.of(!!opts.readOnly)),
98
+ EditorView.updateListener.of((u) => {
99
+ if (u.docChanged && opts.onChange)
100
+ opts.onChange(u.state.doc.toString());
101
+ }),
102
+ ],
103
+ }),
104
+ });
105
+ const handle = {
106
+ view,
107
+ getValue: () => view.state.doc.toString(),
108
+ setValue(text) {
109
+ if (view.state.doc.toString() === text)
110
+ return;
111
+ view.dispatch({
112
+ changes: { from: 0, to: view.state.doc.length, insert: text },
113
+ });
114
+ },
115
+ setMode(mode) {
116
+ view.dispatch({ effects: language.reconfigure(languageForMode(mode)) });
117
+ },
118
+ setReadOnly(ro) {
119
+ view.dispatch({
120
+ effects: readonly.reconfigure(EditorState.readOnly.of(ro)),
121
+ });
122
+ },
123
+ undo: () => void undo(view),
124
+ redo: () => void redo(view),
125
+ canUndo: () => undoDepth(view.state) > 0,
126
+ canRedo: () => redoDepth(view.state) > 0,
127
+ focus: () => view.focus(),
128
+ refresh: () => view.requestMeasure(),
129
+ destroy: () => view.destroy(),
130
+ };
131
+ return handle;
132
+ }
@@ -1,7 +1,15 @@
1
- import { Component as WebComponent, ElementCreator } from 'tosijs';
2
- export declare class CodeEditor extends WebComponent {
1
+ import { Component as WebComponent, ElementCreator, PartsMap } from 'tosijs';
2
+ import type { CmHandle } from './code-editor-cm';
3
+ interface CodeEditorParts extends PartsMap {
4
+ host: HTMLDivElement;
5
+ }
6
+ export declare class CodeEditor extends WebComponent<CodeEditorParts> {
3
7
  static preferredTagName: string;
4
8
  private source;
9
+ private _handle;
10
+ private _loadPromise;
11
+ private _appliedMode;
12
+ private _appliedDisabled;
5
13
  get value(): string;
6
14
  set value(text: string);
7
15
  private _original;
@@ -12,16 +20,16 @@ export declare class CodeEditor extends WebComponent {
12
20
  showDiff(on: boolean): void;
13
21
  static initAttributes: {
14
22
  mode: string;
15
- theme: string;
16
23
  disabled: boolean;
17
24
  };
18
25
  role: string;
19
- private _ace;
20
- private _editor;
21
- private _editorPromise;
22
- options: any;
23
- get ace(): any;
24
- get editor(): any;
26
+ /** The underlying CodeMirror EditorView (undefined until loaded). */
27
+ get editor(): CmHandle['view'] | undefined;
28
+ undo(): void;
29
+ redo(): void;
30
+ canUndo(): boolean;
31
+ canRedo(): boolean;
32
+ content: () => HTMLDivElement[];
25
33
  static shadowStyleSpec: {
26
34
  ':host': {
27
35
  display: string;
@@ -29,9 +37,20 @@ export declare class CodeEditor extends WebComponent {
29
37
  width: string;
30
38
  height: string;
31
39
  };
40
+ '[part="host"]': {
41
+ height: string;
42
+ };
43
+ '.cm-editor': {
44
+ height: string;
45
+ };
46
+ '.cm-scroller': {
47
+ outline: string;
48
+ fontFamily: string;
49
+ };
32
50
  };
33
51
  onResize(): void;
34
52
  connectedCallback(): void;
35
53
  render(): void;
36
54
  }
37
55
  export declare const codeEditor: ElementCreator<CodeEditor>;
56
+ export {};
@@ -1,12 +1,12 @@
1
1
  /*#
2
2
  # code
3
3
 
4
- An [ACE Editor](https://ace.c9.io/) wrapper.
4
+ A [CodeMirror 6](https://codemirror.net/) wrapper.
5
5
 
6
6
  Sometimes, it's nice to be able to just toss a code-editor in a web-page.
7
7
 
8
- `<tosi-code>`'s `value` is the code it contains. Its `mode` attribute sets the language, and you can further configure
9
- the ACE editor instance via its `options` property.
8
+ `<tosi-code>`'s `value` is the code it contains. Its `mode` attribute sets the
9
+ language (`javascript`, `typescript`, `tjs`, `css`, `html`, `markdown`).
10
10
 
11
11
  ```html
12
12
  <tosi-code style="width: 100%; height: 100%" mode="css">
@@ -16,51 +16,33 @@ body {
16
16
  </tosi-code>
17
17
  ```
18
18
 
19
- The `<tosi-code>` element has an `editor` property that gives you its ACE editor instance,
20
- and an `ace` property that returns the `ace` module, giving you complete access to the
21
- [Ace API](https://ace.c9.io/api/index.html).
19
+ The `<tosi-code>` element has an `editor` property that gives you its CodeMirror
20
+ [`EditorView`](https://codemirror.net/docs/ref/#view.EditorView), and
21
+ `undo()` / `redo()` / `canUndo()` / `canRedo()` methods for history control.
22
+
23
+ CodeMirror is loaded lazily on first use — a page with no `<tosi-code>` bundles
24
+ none of it.
22
25
  */
23
26
  /*{ "parent": "Components" }*/
24
- import { Component as WebComponent } from 'tosijs';
25
- import { scriptTag } from './via-tag';
27
+ import { Component as WebComponent, elements } from 'tosijs';
26
28
  import { tosiDiff } from './diff';
27
- const ACE_BASE_URL = 'https://cdnjs.cloudflare.com/ajax/libs/ace/1.23.2/';
28
- const DEFAULT_THEME = 'ace/theme/tomorrow';
29
- const getAce = async () => {
30
- const { ace } = await scriptTag(`${ACE_BASE_URL}ace.min.js`);
31
- return ace;
32
- };
33
- const makeCodeEditor = async (codeElement, mode = 'html', options = {}, theme = DEFAULT_THEME) => {
34
- const ace = await getAce();
35
- ace.config.set('basePath', ACE_BASE_URL);
36
- const editor = ace.edit(codeElement, {
37
- mode: `ace/mode/${mode}`,
38
- tabSize: 2,
39
- useSoftTabs: true,
40
- useWorker: false,
41
- // Wrap wide lines instead of scrolling horizontally; indentedSoftWrap keeps
42
- // wrapped continuations aligned under the line's indent.
43
- wrap: true,
44
- indentedSoftWrap: true,
45
- ...options,
46
- });
47
- editor.setTheme(theme);
48
- return { ace, editor };
49
- };
29
+ const { div } = elements;
50
30
  export class CodeEditor extends WebComponent {
51
31
  static preferredTagName = 'tosi-code';
52
32
  source = '';
33
+ _handle;
34
+ _loadPromise;
35
+ _appliedMode = '';
36
+ _appliedDisabled;
53
37
  get value() {
54
- return this.editor === undefined ? this.source : this.editor.getValue();
38
+ return this._handle ? this._handle.getValue() : this.source;
55
39
  }
56
40
  set value(text) {
57
- if (this.editor === undefined) {
58
- this.source = text;
41
+ if (this._handle) {
42
+ this._handle.setValue(text);
59
43
  }
60
44
  else {
61
- this.editor.setValue(text);
62
- this.editor.clearSelection();
63
- this.editor.session.getUndoManager().reset();
45
+ this.source = text;
64
46
  }
65
47
  }
66
48
  // Baseline for `showDiff()` — the version to diff the current `value` against.
@@ -72,9 +54,9 @@ export class CodeEditor extends WebComponent {
72
54
  set original(text) {
73
55
  this._original = text;
74
56
  }
75
- // Diff overlay — deliberately built only on the editor's public surface
76
- // (`value` + `original`) and the tosi-diff component, never the underlying
77
- // editor's API, so it survives a future Ace → CodeMirror swap untouched.
57
+ // Diff overlay — built only on the editor's public surface (`value` + `original`)
58
+ // and the tosi-diff component, never the underlying editor's API, so it is
59
+ // editor-agnostic (this is why it survived the Ace → CodeMirror swap untouched).
78
60
  diffOverlay;
79
61
  get showingDiff() {
80
62
  return this.diffOverlay !== undefined && !this.diffOverlay.hidden;
@@ -103,20 +85,27 @@ export class CodeEditor extends WebComponent {
103
85
  }
104
86
  static initAttributes = {
105
87
  mode: 'javascript',
106
- theme: DEFAULT_THEME,
107
88
  disabled: false,
108
89
  };
109
90
  role = 'code editor';
110
- _ace;
111
- _editor;
112
- _editorPromise;
113
- options = {};
114
- get ace() {
115
- return this._ace;
116
- }
91
+ /** The underlying CodeMirror EditorView (undefined until loaded). */
117
92
  get editor() {
118
- return this._editor;
93
+ return this._handle?.view;
94
+ }
95
+ // History control — so consumers use these instead of reaching into `editor`.
96
+ undo() {
97
+ this._handle?.undo();
98
+ }
99
+ redo() {
100
+ this._handle?.redo();
101
+ }
102
+ canUndo() {
103
+ return this._handle?.canUndo() ?? false;
119
104
  }
105
+ canRedo() {
106
+ return this._handle?.canRedo() ?? false;
107
+ }
108
+ content = () => [div({ part: 'host' })];
120
109
  static shadowStyleSpec = {
121
110
  ':host': {
122
111
  display: 'block',
@@ -124,32 +113,49 @@ export class CodeEditor extends WebComponent {
124
113
  width: '100%',
125
114
  height: '100%',
126
115
  },
116
+ '[part="host"]': { height: '100%' },
117
+ '.cm-editor': { height: '100%' },
118
+ '.cm-scroller': {
119
+ outline: 'none',
120
+ fontFamily: "Menlo, Monaco, Consolas, 'Courier New', monospace",
121
+ },
127
122
  };
128
123
  onResize() {
129
- if (this.editor !== undefined) {
130
- this.editor.resize(true);
131
- }
124
+ this._handle?.refresh();
132
125
  }
133
126
  connectedCallback() {
134
127
  super.connectedCallback();
135
128
  if (this.source === '') {
136
129
  this.value = this.textContent !== null ? this.textContent.trim() : '';
137
130
  }
138
- if (this._editorPromise === undefined) {
139
- this._editorPromise = makeCodeEditor(this, this.mode, this.options, this.theme);
140
- this._editorPromise.then(({ ace, editor }) => {
141
- this._ace = ace;
142
- this._editor = editor;
143
- editor.setValue(this.source, 1);
144
- editor.clearSelection();
145
- editor.session.getUndoManager().reset();
131
+ if (this._loadPromise === undefined) {
132
+ // Lazy chunk — CodeMirror only enters the bundle here, on first editor use.
133
+ this._loadPromise = import('./code-editor-cm').then(({ createCmEditor }) => {
134
+ const handle = createCmEditor(this.parts.host, {
135
+ value: this.source,
136
+ mode: this.mode,
137
+ readOnly: this.disabled,
138
+ root: this.shadowRoot ?? undefined,
139
+ onChange: (value) => this.dispatchEvent(new CustomEvent('change', { detail: { value } })),
140
+ });
141
+ this._handle = handle;
142
+ this._appliedMode = this.mode;
143
+ this._appliedDisabled = this.disabled;
144
+ return handle;
146
145
  });
147
146
  }
148
147
  }
149
148
  render() {
150
149
  super.render();
151
- if (this._editorPromise !== undefined) {
152
- this._editorPromise.then(({ editor }) => editor.setReadOnly(this.disabled));
150
+ if (this._handle) {
151
+ if (this.disabled !== this._appliedDisabled) {
152
+ this._handle.setReadOnly(this.disabled);
153
+ this._appliedDisabled = this.disabled;
154
+ }
155
+ if (this.mode !== this._appliedMode) {
156
+ this._handle.setMode(this.mode);
157
+ this._appliedMode = this.mode;
158
+ }
153
159
  }
154
160
  }
155
161
  }