symfony-expression-editor 0.1.0 → 0.2.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
@@ -1,6 +1,13 @@
1
1
  CHANGELOG
2
2
  =========
3
3
 
4
+ 0.2.0
5
+ ---
6
+ * Add some animations to reduce flickering
7
+ * Make sure there is no layout shift
8
+ * Make element cloning work as intended
9
+ * Use shadow DOM
10
+
4
11
  0.1.0
5
12
  ---
6
13
 
package/README.md CHANGED
@@ -14,7 +14,7 @@ Configuring CodeMirror and making it blend into UI can be tricky
14
14
 
15
15
  ```html
16
16
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
17
- <script type="module" src="https://cdn.jsdelivr.net/npm/symfony-expression-editor@1.0.0/+esm"></script>
17
+ <script type="module" src="https://esm.sh/symfony-expression-editor"></script>
18
18
  <textarea class="form-control" is="expression-editor" rows="1">'foobar' starts with 'foo'</textarea>
19
19
  ```
20
20
 
package/dist/index.cjs CHANGED
@@ -1162,10 +1162,9 @@ class LintState {
1162
1162
  this.selected = selected;
1163
1163
  }
1164
1164
  static init(diagnostics, panel, state$1) {
1165
- let markedDiagnostics = diagnostics;
1166
1165
  let diagnosticFilter = state$1.facet(lintConfig).markerFilter;
1167
1166
  if (diagnosticFilter)
1168
- markedDiagnostics = diagnosticFilter(markedDiagnostics, state$1);
1167
+ diagnostics = diagnosticFilter(diagnostics, state$1);
1169
1168
  let sorted = diagnostics.slice().sort((a, b) => a.from - b.from || a.to - b.to);
1170
1169
  let deco = new state.RangeSetBuilder(), active = [], pos = 0;
1171
1170
  for (let i = 0; ; ) {
@@ -1749,7 +1748,8 @@ const bootstrap = view.EditorView.theme({
1749
1748
  padding: "0 0.75rem"
1750
1749
  },
1751
1750
  ".cm-content": {
1752
- padding: "0.375rem 0"
1751
+ padding: "0.375rem 0",
1752
+ animation: "fade-to-colors 0.5s ease-out 0s forwards"
1753
1753
  },
1754
1754
  "&:not(.cm-focused) .cm-selectionBackground, &:not(.cm-focused) .cm-activeLine, &:not(.cm-focused) .cm-activeLineGutter": {
1755
1755
  backgroundColor: "transparent"
@@ -1764,13 +1764,17 @@ const bootstrap = view.EditorView.theme({
1764
1764
  overflow: "auto",
1765
1765
  height: "100%",
1766
1766
  lineHeight: 1.85,
1767
- flexGrow: 1
1767
+ flexGrow: 1,
1768
+ animation: "fade 0.5s ease-out 0s forwards"
1768
1769
  },
1769
1770
  ".cm-gutters": {
1770
1771
  height: "100% !important",
1771
1772
  color: "var(--bs-tertiary-color)",
1772
1773
  backgroundColor: "var(--bs-tertiary-bg)"
1773
- }
1774
+ },
1775
+ "@keyframes slide-left": { from: { transform: "translateX(-100%)", opacity: 0 } },
1776
+ "@keyframes fade-to-colors": { from: { filter: "grayscale(100%)" } },
1777
+ "@keyframes fade": { from: { opacity: 0 } }
1774
1778
  });
1775
1779
 
1776
1780
  const darkTheme = view.EditorView.theme({
@@ -1843,41 +1847,57 @@ var light = [
1843
1847
 
1844
1848
  const getTheme = (el) => ({ light, dark })[el.dataset.bsTheme || "light"];
1845
1849
  const mutationObserver = new MutationObserver(
1846
- (changes) => changes.map((mutation) => mutation.target).filter((mutationTarget) => mutationTarget instanceof HTMLElement).map((themeContainer) => ({ transaction: { effects: themes.get(themeContainer).reconfigure(getTheme(themeContainer)) }, editors: editors.get(themeContainer) })).forEach(({ transaction, editors: editors2 }) => editors2.forEach((editor) => editor.dispatch(transaction)))
1850
+ (changes) => changes.map((mutation) => mutation.target).filter((mutationTarget) => mutationTarget instanceof HTMLElement).map((themeContainer) => editors.get(themeContainer).forEach((editor) => setTheme(themeContainer, editor)))
1847
1851
  );
1852
+ const setTheme = (themeContainer, editor) => editor.dispatch({ effects: themes.get(editor).reconfigure(getTheme(themeContainer)) });
1848
1853
  const themes = /* @__PURE__ */ new WeakMap();
1849
1854
  const editors = /* @__PURE__ */ new WeakMap();
1850
1855
  class ExpressionEditor extends HTMLTextAreaElement {
1851
1856
  constructor() {
1852
1857
  super();
1853
- const themeContainer = this.closest("[data-bs-theme]") || document.documentElement;
1854
- if (!themes.has(themeContainer)) {
1855
- mutationObserver.observe(themeContainer, { attributes: true, attributeFilter: ["data-bs-theme"] });
1856
- themes.set(themeContainer, new state.Compartment());
1857
- editors.set(themeContainer, /* @__PURE__ */ new Set());
1858
- }
1858
+ this.theme = new state.Compartment();
1859
+ this.instanceStyles = new state.Compartment();
1859
1860
  this.dom = document.createElement("div");
1861
+ const shadow = this.dom.attachShadow({ mode: "closed" });
1860
1862
  this.editorView = new view.EditorView({
1861
1863
  extensions: [
1862
- view.EditorView.editorAttributes.of({ style: `min-height: calc(0.75rem + ${this.rows} * 0.8125 * 1.85rem + 2px); height: calc(0.75rem + ${this.rows} * 0.8125 * 1.85rem + 2px)` }),
1864
+ this.instanceStyles.of(view.EditorView.theme({})),
1863
1865
  basicSetup({
1864
1866
  useLineNumbers: JSON.parse(this.dataset.lineNumbers || "false")
1865
1867
  }),
1866
- themes.get(themeContainer).of(getTheme(themeContainer)),
1868
+ this.theme.of(light),
1867
1869
  view.keymap.of([...commands.defaultKeymap, { key: "Tab", run: autocomplete.acceptCompletion }]),
1868
- codemirrorLangEl.expressionlanguage(JSON.parse(this.dataset.config || "{}"), [getTheme(themeContainer)]),
1870
+ codemirrorLangEl.expressionlanguage(JSON.parse(this.dataset.config || "{}")),
1869
1871
  view.EditorView.updateListener.of((e) => {
1870
1872
  if (e.docChanged) {
1871
1873
  this.value = e.state.doc.toString();
1872
1874
  }
1873
1875
  })
1874
1876
  ],
1875
- parent: this.dom,
1877
+ root: shadow,
1878
+ parent: shadow,
1876
1879
  doc: this.value.trim()
1877
1880
  });
1881
+ themes.set(this.editorView, this.theme);
1882
+ }
1883
+ connectedCallback() {
1884
+ const themeContainer = this.closest("[data-bs-theme]") || document.documentElement;
1885
+ if (!editors.has(themeContainer)) {
1886
+ editors.set(themeContainer, /* @__PURE__ */ new Set());
1887
+ mutationObserver.observe(themeContainer, { attributes: true, attributeFilter: ["data-bs-theme"] });
1888
+ }
1889
+ if (editors.get(themeContainer).has(this.editorView)) {
1890
+ return;
1891
+ }
1892
+ setTheme(themeContainer, this.editorView);
1878
1893
  editors.get(themeContainer).add(this.editorView);
1894
+ this.editorView.dispatch({
1895
+ effects: this.instanceStyles.reconfigure(view.EditorView.theme({
1896
+ "&": { minHeight: this.getBoundingClientRect().height + "px" },
1897
+ "& .cm-gutter": { minHeight: `${this.getBoundingClientRect().height - 2}px` }
1898
+ }))
1899
+ });
1879
1900
  this.replaceWith(this.dom);
1880
- this.hidden = true;
1881
1901
  this.dom.appendChild(this);
1882
1902
  }
1883
1903
  }
package/dist/index.d.cts CHANGED
@@ -1,7 +1,10 @@
1
1
  declare class ExpressionEditor extends HTMLTextAreaElement {
2
2
  private readonly dom;
3
3
  private readonly editorView;
4
+ private readonly theme;
5
+ private readonly instanceStyles;
4
6
  constructor();
7
+ connectedCallback(): void;
5
8
  }
6
9
 
7
10
  export { ExpressionEditor };
package/dist/index.d.mts CHANGED
@@ -1,7 +1,10 @@
1
1
  declare class ExpressionEditor extends HTMLTextAreaElement {
2
2
  private readonly dom;
3
3
  private readonly editorView;
4
+ private readonly theme;
5
+ private readonly instanceStyles;
4
6
  constructor();
7
+ connectedCallback(): void;
5
8
  }
6
9
 
7
10
  export { ExpressionEditor };
package/dist/index.mjs CHANGED
@@ -1160,10 +1160,9 @@ class LintState {
1160
1160
  this.selected = selected;
1161
1161
  }
1162
1162
  static init(diagnostics, panel, state) {
1163
- let markedDiagnostics = diagnostics;
1164
1163
  let diagnosticFilter = state.facet(lintConfig).markerFilter;
1165
1164
  if (diagnosticFilter)
1166
- markedDiagnostics = diagnosticFilter(markedDiagnostics, state);
1165
+ diagnostics = diagnosticFilter(diagnostics, state);
1167
1166
  let sorted = diagnostics.slice().sort((a, b) => a.from - b.from || a.to - b.to);
1168
1167
  let deco = new RangeSetBuilder(), active = [], pos = 0;
1169
1168
  for (let i = 0; ; ) {
@@ -1747,7 +1746,8 @@ const bootstrap = EditorView.theme({
1747
1746
  padding: "0 0.75rem"
1748
1747
  },
1749
1748
  ".cm-content": {
1750
- padding: "0.375rem 0"
1749
+ padding: "0.375rem 0",
1750
+ animation: "fade-to-colors 0.5s ease-out 0s forwards"
1751
1751
  },
1752
1752
  "&:not(.cm-focused) .cm-selectionBackground, &:not(.cm-focused) .cm-activeLine, &:not(.cm-focused) .cm-activeLineGutter": {
1753
1753
  backgroundColor: "transparent"
@@ -1762,13 +1762,17 @@ const bootstrap = EditorView.theme({
1762
1762
  overflow: "auto",
1763
1763
  height: "100%",
1764
1764
  lineHeight: 1.85,
1765
- flexGrow: 1
1765
+ flexGrow: 1,
1766
+ animation: "fade 0.5s ease-out 0s forwards"
1766
1767
  },
1767
1768
  ".cm-gutters": {
1768
1769
  height: "100% !important",
1769
1770
  color: "var(--bs-tertiary-color)",
1770
1771
  backgroundColor: "var(--bs-tertiary-bg)"
1771
- }
1772
+ },
1773
+ "@keyframes slide-left": { from: { transform: "translateX(-100%)", opacity: 0 } },
1774
+ "@keyframes fade-to-colors": { from: { filter: "grayscale(100%)" } },
1775
+ "@keyframes fade": { from: { opacity: 0 } }
1772
1776
  });
1773
1777
 
1774
1778
  const darkTheme = EditorView.theme({
@@ -1841,41 +1845,57 @@ var light = [
1841
1845
 
1842
1846
  const getTheme = (el) => ({ light, dark })[el.dataset.bsTheme || "light"];
1843
1847
  const mutationObserver = new MutationObserver(
1844
- (changes) => changes.map((mutation) => mutation.target).filter((mutationTarget) => mutationTarget instanceof HTMLElement).map((themeContainer) => ({ transaction: { effects: themes.get(themeContainer).reconfigure(getTheme(themeContainer)) }, editors: editors.get(themeContainer) })).forEach(({ transaction, editors: editors2 }) => editors2.forEach((editor) => editor.dispatch(transaction)))
1848
+ (changes) => changes.map((mutation) => mutation.target).filter((mutationTarget) => mutationTarget instanceof HTMLElement).map((themeContainer) => editors.get(themeContainer).forEach((editor) => setTheme(themeContainer, editor)))
1845
1849
  );
1850
+ const setTheme = (themeContainer, editor) => editor.dispatch({ effects: themes.get(editor).reconfigure(getTheme(themeContainer)) });
1846
1851
  const themes = /* @__PURE__ */ new WeakMap();
1847
1852
  const editors = /* @__PURE__ */ new WeakMap();
1848
1853
  class ExpressionEditor extends HTMLTextAreaElement {
1849
1854
  constructor() {
1850
1855
  super();
1851
- const themeContainer = this.closest("[data-bs-theme]") || document.documentElement;
1852
- if (!themes.has(themeContainer)) {
1853
- mutationObserver.observe(themeContainer, { attributes: true, attributeFilter: ["data-bs-theme"] });
1854
- themes.set(themeContainer, new Compartment());
1855
- editors.set(themeContainer, /* @__PURE__ */ new Set());
1856
- }
1856
+ this.theme = new Compartment();
1857
+ this.instanceStyles = new Compartment();
1857
1858
  this.dom = document.createElement("div");
1859
+ const shadow = this.dom.attachShadow({ mode: "closed" });
1858
1860
  this.editorView = new EditorView({
1859
1861
  extensions: [
1860
- EditorView.editorAttributes.of({ style: `min-height: calc(0.75rem + ${this.rows} * 0.8125 * 1.85rem + 2px); height: calc(0.75rem + ${this.rows} * 0.8125 * 1.85rem + 2px)` }),
1862
+ this.instanceStyles.of(EditorView.theme({})),
1861
1863
  basicSetup({
1862
1864
  useLineNumbers: JSON.parse(this.dataset.lineNumbers || "false")
1863
1865
  }),
1864
- themes.get(themeContainer).of(getTheme(themeContainer)),
1866
+ this.theme.of(light),
1865
1867
  keymap.of([...defaultKeymap, { key: "Tab", run: acceptCompletion }]),
1866
- expressionlanguage(JSON.parse(this.dataset.config || "{}"), [getTheme(themeContainer)]),
1868
+ expressionlanguage(JSON.parse(this.dataset.config || "{}")),
1867
1869
  EditorView.updateListener.of((e) => {
1868
1870
  if (e.docChanged) {
1869
1871
  this.value = e.state.doc.toString();
1870
1872
  }
1871
1873
  })
1872
1874
  ],
1873
- parent: this.dom,
1875
+ root: shadow,
1876
+ parent: shadow,
1874
1877
  doc: this.value.trim()
1875
1878
  });
1879
+ themes.set(this.editorView, this.theme);
1880
+ }
1881
+ connectedCallback() {
1882
+ const themeContainer = this.closest("[data-bs-theme]") || document.documentElement;
1883
+ if (!editors.has(themeContainer)) {
1884
+ editors.set(themeContainer, /* @__PURE__ */ new Set());
1885
+ mutationObserver.observe(themeContainer, { attributes: true, attributeFilter: ["data-bs-theme"] });
1886
+ }
1887
+ if (editors.get(themeContainer).has(this.editorView)) {
1888
+ return;
1889
+ }
1890
+ setTheme(themeContainer, this.editorView);
1876
1891
  editors.get(themeContainer).add(this.editorView);
1892
+ this.editorView.dispatch({
1893
+ effects: this.instanceStyles.reconfigure(EditorView.theme({
1894
+ "&": { minHeight: this.getBoundingClientRect().height + "px" },
1895
+ "& .cm-gutter": { minHeight: `${this.getBoundingClientRect().height - 2}px` }
1896
+ }))
1897
+ });
1877
1898
  this.replaceWith(this.dom);
1878
- this.hidden = true;
1879
1899
  this.dom.appendChild(this);
1880
1900
  }
1881
1901
  }
package/example.html CHANGED
@@ -1,4 +1,14 @@
1
1
  <html>
2
+ <head>
3
+ <style>
4
+ textarea[is="expression-editor"] {
5
+ animation: fadeTextIn 1.0s ease-in-out 0s forwards;
6
+ font-family: monospace;
7
+ }
8
+
9
+ @keyframes fadeTextIn { from { color: rgba(var(--bs-body-color-rgb), 0); } }
10
+ </style>
11
+ </head>
2
12
  <body class="bg-body-secondary container">
3
13
  <script type="importmap">
4
14
  {
@@ -18,7 +28,7 @@
18
28
  "w3c-keyname": "https://esm.sh/*w3c-keyname@2.2.8",
19
29
  "crelt": "https://esm.sh/*crelt@1.0.6",
20
30
  "@marijn/find-cluster-break": "https://esm.sh/*@marijn/find-cluster-break@1.0.2",
21
- "@valtzu/codemirror-lang-el": "https://esm.sh/*@valtzu/codemirror-lang-el@0.10.0"
31
+ "@valtzu/codemirror-lang-el": "https://esm.sh/*@valtzu/codemirror-lang-el@1.0.0"
22
32
  }
23
33
  }
24
34
  </script>
@@ -34,13 +44,22 @@
34
44
  <input class="form-check-input" type="checkbox" role="switch" id="light-switch" onchange="this.closest('.section').dataset.bsTheme=this.checked ? 'light' : 'dark'">
35
45
  <label class="form-check-label" for="light-switch">Lights</label>
36
46
  </div>
37
- <div>
38
- <textarea rows="5" class="form-control" is="expression-editor" data-line-numbers="true" data-config="{&quot;identifiers&quot;:[{&quot;name&quot;:&quot;x&quot;}]}">
39
- x == x + 1
47
+ <div id="template">
48
+ <textarea rows="3" class="form-control" is="expression-editor" data-line-numbers="true" data-config="{&quot;identifiers&quot;:[{&quot;name&quot;:&quot;x&quot;}]}">x == x + 1
40
49
  && 'foobar' starts with 'foo'
41
- && x == x + 1
42
- </textarea>
50
+ && x == x + 1</textarea>
51
+ </div>
52
+ <div>
53
+ <button type="button" class="btn btn-sm btn-secondary mt-3" id="clone">Clone</button>
43
54
  </div>
44
55
  </div>
56
+ <script type="module">
57
+ document.getElementById('clone').addEventListener('click', function () {
58
+ const template = document.getElementById('template');
59
+ const clone = template.cloneNode(true);
60
+ clone.removeAttribute('id');
61
+ template.parentNode.insertBefore(clone, this.parentElement);
62
+ });
63
+ </script>
45
64
  </body>
46
65
  </html>
package/package.json CHANGED
@@ -1,6 +1,5 @@
1
1
  {
2
2
  "name": "symfony-expression-editor",
3
- "version": "0.1.0",
4
3
  "main": "./dist/index.cjs",
5
4
  "module": "./dist/index.mjs",
6
5
  "types": "./dist/index.d.cts",
@@ -28,12 +27,11 @@
28
27
  "@codemirror/state": "^6.5.2",
29
28
  "@codemirror/view": "^6.36.3",
30
29
  "@lezer/highlight": "^1.2.1",
31
- "@valtzu/codemirror-lang-el": "^0.10.0",
30
+ "@valtzu/codemirror-lang-el": "^1.0.0",
32
31
  "codemirror": "^6.0.1"
33
32
  },
34
33
  "devDependencies": {
35
34
  "pkgroll": "^2.11.2",
36
- "tsx": "^4.19.3",
37
35
  "typescript": "^5.8.2"
38
36
  },
39
37
  "license": "MIT",
@@ -44,5 +42,6 @@
44
42
  "publishConfig": {
45
43
  "access": "public",
46
44
  "registry": "https://registry.npmjs.org/"
47
- }
45
+ },
46
+ "version": "0.2.0"
48
47
  }