瀏覽代碼

Merge pull request #8584 from AlbertHilb/ShadowDOM

Move CodeMirror HTML tree and related CSS to shadow DOM
Steven Silvester 4 年之前
父節點
當前提交
f7b7ee7d27

+ 1 - 0
dev_mode/package.json

@@ -78,6 +78,7 @@
     "style-loader": "~1.0.1",
     "svg-url-loader": "~3.0.3",
     "terser-webpack-plugin": "^2.3.0",
+    "to-string-loader": "^1.1.6",
     "url-loader": "~3.0.0",
     "webpack": "5.0.0-beta.21",
     "webpack-bundle-analyzer": "^3.6.0",

+ 2 - 2
packages/codeeditor/test/widget.spec.ts

@@ -135,7 +135,7 @@ describe('CodeEditorWrapper', () => {
         const editor = widget.editor as LogEditor;
         MessageLoop.sendMessage(widget, Widget.ResizeMessage.UnknownSize);
         editor.methods = [];
-        simulate(editor.editor.getInputField(), 'focus');
+        simulate(editor.editor.getInputField(), 'focus', { composed: true });
         expect(editor.methods).toEqual(['refresh']);
       });
     });
@@ -197,7 +197,7 @@ describe('CodeEditorWrapper', () => {
       editor.methods = [];
       MessageLoop.sendMessage(widget, Widget.ResizeMessage.UnknownSize);
       expect(editor.methods).toEqual([]);
-      simulate(editor.editor.getInputField(), 'focus');
+      simulate(editor.editor.getInputField(), 'focus', { composed: true });
       expect(editor.methods).toEqual(['refresh']);
     });
   });

+ 16 - 27
packages/codemirror-extension/src/index.ts

@@ -167,17 +167,6 @@ function activateEditorCommands(
     }
 
     theme = (settings.get('theme').composite as string | null) || theme;
-
-    // Lazy loading of theme stylesheets
-    if (theme !== 'jupyter' && theme !== 'default') {
-      const filename =
-        theme === 'solarized light' || theme === 'solarized dark'
-          ? 'solarized'
-          : theme;
-
-      await import(`codemirror/theme/${filename}.css`);
-    }
-
     scrollPastEnd =
       (settings.get('scrollPastEnd').composite as boolean | null) ??
       scrollPastEnd;
@@ -201,14 +190,14 @@ function activateEditorCommands(
   function updateTracker(): void {
     tracker.forEach(widget => {
       if (widget.content.editor instanceof CodeMirrorEditor) {
-        const cm = widget.content.editor.editor;
-        cm.setOption('keyMap', keyMap);
-        cm.setOption('theme', theme);
-        cm.setOption('scrollPastEnd', scrollPastEnd);
-        cm.setOption('styleActiveLine', styleActiveLine);
-        cm.setOption('styleSelectedText', styleSelectedText);
-        cm.setOption('selectionPointer', selectionPointer);
-        cm.setOption('lineWiseCopyCut', lineWiseCopyCut);
+        const { editor } = widget.content;
+        editor.setOption('keyMap', keyMap);
+        editor.setOption('lineWiseCopyCut', lineWiseCopyCut);
+        editor.setOption('scrollPastEnd', scrollPastEnd);
+        editor.setOption('selectionPointer', selectionPointer);
+        editor.setOption('styleActiveLine', styleActiveLine);
+        editor.setOption('styleSelectedText', styleSelectedText);
+        editor.setOption('theme', theme);
       }
     });
   }
@@ -233,14 +222,14 @@ function activateEditorCommands(
    */
   tracker.widgetAdded.connect((sender, widget) => {
     if (widget.content.editor instanceof CodeMirrorEditor) {
-      const cm = widget.content.editor.editor;
-      cm.setOption('keyMap', keyMap);
-      cm.setOption('theme', theme);
-      cm.setOption('scrollPastEnd', scrollPastEnd);
-      cm.setOption('styleActiveLine', styleActiveLine);
-      cm.setOption('styleSelectedText', styleSelectedText);
-      cm.setOption('selectionPointer', selectionPointer);
-      cm.setOption('lineWiseCopyCut', lineWiseCopyCut);
+      const { editor } = widget.content;
+      editor.setOption('keyMap', keyMap);
+      editor.setOption('lineWiseCopyCut', lineWiseCopyCut);
+      editor.setOption('selectionPointer', selectionPointer);
+      editor.setOption('scrollPastEnd', scrollPastEnd);
+      editor.setOption('styleActiveLine', styleActiveLine);
+      editor.setOption('styleSelectedText', styleSelectedText);
+      editor.setOption('theme', theme);
     }
   });
 

+ 74 - 4
packages/codemirror/src/editor.ts

@@ -49,6 +49,11 @@ import 'codemirror/keymap/emacs.js';
 import 'codemirror/keymap/sublime.js';
 // import 'codemirror/keymap/vim.js';  lazy loading of vim mode is available in ../codemirror-extension/index.ts
 
+// @ts-expect-error
+import shadowCss from '!!to-string-loader!css-loader!../style/shadow.css';
+// @ts-expect-error
+import jupyterThemeCSS from '!!to-string-loader!css-loader!../style/jupyter-theme.css';
+
 /**
  * The class name added to CodeMirrorWidget instances.
  */
@@ -69,6 +74,11 @@ const COLLABORATOR_CURSOR_CLASS = 'jp-CollaboratorCursor';
  */
 const COLLABORATOR_HOVER_CLASS = 'jp-CollaboratorCursor-hover';
 
+/**
+ * The id of the style element containing CodeMirror theme CSS.
+ */
+const CODEMIRROR_THEME_STYLE_ID = 'jp-CodeMirror-theme';
+
 /**
  * The key code for the up arrow key.
  */
@@ -93,6 +103,19 @@ export class CodeMirrorEditor implements CodeEditor.IEditor {
    */
   constructor(options: CodeMirrorEditor.IOptions) {
     const host = (this.host = options.host);
+    // Attach shadow root to host if host does not have already it.
+    const shadowRoot = host.shadowRoot || host.attachShadow({ mode: 'open' });
+
+    // Add a style element with the CodeMirror core CSS to the shadow root.
+    const shadowStyleEl = document.createElement('style');
+    shadowStyleEl.textContent = shadowCss;
+    shadowRoot.appendChild(shadowStyleEl);
+    // Add CodeMirror theme stylesheet.
+    const themeStyleEl = document.createElement('style');
+    themeStyleEl.id = CODEMIRROR_THEME_STYLE_ID;
+    themeStyleEl.textContent = jupyterThemeCSS;
+    shadowRoot.appendChild(themeStyleEl);
+
     host.classList.add(EDITOR_CLASS);
     host.classList.add('jp-Editor');
     host.addEventListener('focus', this, true);
@@ -114,7 +137,10 @@ export class CodeMirrorEditor implements CodeEditor.IEditor {
       ...CodeMirrorEditor.defaultConfig,
       ...config
     });
-    const editor = (this._editor = Private.createEditor(host, fullConfig));
+    const editor = (this._editor = Private.createEditor(
+      shadowRoot,
+      fullConfig
+    ));
 
     const doc = editor.getDoc();
 
@@ -294,7 +320,13 @@ export class CodeMirrorEditor implements CodeEditor.IEditor {
     // Don't bother setting the option if it is already the same.
     if (this._config[option] !== value) {
       this._config[option] = value;
-      Private.setOption(this.editor, option, value, this._config);
+      Private.setOption(
+        this.editor,
+        this.host.shadowRoot!,
+        option,
+        value,
+        this._config
+      );
     }
   }
 
@@ -355,7 +387,7 @@ export class CodeMirrorEditor implements CodeEditor.IEditor {
    * Test whether the editor has keyboard focus.
    */
   hasFocus(): boolean {
-    return this._editor.getWrapperElement().contains(document.activeElement);
+    return this.host === document.activeElement;
   }
 
   /**
@@ -1302,7 +1334,7 @@ export namespace CodeMirrorEditor {
  */
 namespace Private {
   export function createEditor(
-    host: HTMLElement,
+    host: ShadowRoot,
     config: CodeMirrorEditor.IConfig
   ): CodeMirror.Editor {
     const {
@@ -1432,11 +1464,46 @@ namespace Private {
     );
   }
 
+  /**
+   * Set the stylesheet for the selected theme.
+   */
+  async function setTheme(
+    editor: CodeMirror.Editor,
+    shadowRoot: ShadowRoot,
+    theme: string
+  ): Promise<void> {
+    let stylesheet: string;
+
+    if (theme === 'jupyter') {
+      stylesheet = jupyterThemeCSS;
+    } else if (theme === 'default') {
+      stylesheet = '';
+    } else {
+      // Load the theme stylesheet.
+      const filename =
+        theme === 'solarized light' || theme === 'solarized dark'
+          ? 'solarized'
+          : theme;
+
+      const module = await import(
+        `!!to-string-loader!css-loader!codemirror/theme/${filename}.css`
+      );
+      stylesheet = module.default;
+    }
+
+    const themeStyleEl = shadowRoot.getElementById(
+      CODEMIRROR_THEME_STYLE_ID
+    ) as HTMLStyleElement;
+    themeStyleEl.textContent = stylesheet;
+    editor.setOption('theme', theme);
+  }
+
   /**
    * Set a config option for the editor.
    */
   export function setOption<K extends keyof CodeMirrorEditor.IConfig>(
     editor: CodeMirror.Editor,
+    shadowRoot: ShadowRoot,
     option: K,
     value: CodeMirrorEditor.IConfig[K],
     config: CodeMirrorEditor.IConfig
@@ -1510,6 +1577,9 @@ namespace Private {
         editor.setOption('foldGutter', value);
         editor.setOption('gutters', getActiveGutters(config));
         break;
+      case 'theme':
+        void setTheme(editor, shadowRoot, value);
+        break;
       default:
         editor.setOption(option, value);
         break;

+ 5 - 118
packages/codemirror/style/base.css

@@ -17,7 +17,7 @@
   padding: 0 var(--jp-code-padding);
 }
 
-.jp-CodeMirrorEditor[data-type='inline'] .CodeMirror-dialog {
+:host([data-type='inline']) .CodeMirror-dialog {
   background-color: var(--jp-layout-color0);
   color: var(--jp-content-font-color1);
 }
@@ -32,17 +32,17 @@
   padding: 0 8px;
 }
 
-.jp-CodeMirrorEditor {
+:host {
   cursor: text;
 }
 
-.jp-CodeMirrorEditor[data-type='inline'] .CodeMirror-cursor {
+:host([data-type='inline']) .CodeMirror-cursor {
   border-left: var(--jp-code-cursor-width0) solid var(--jp-editor-cursor-color);
 }
 
 /* When zoomed out 67% and 33% on a screen of 1440 width x 900 height */
 @media screen and (min-width: 2138px) and (max-width: 4319px) {
-  .jp-CodeMirrorEditor[data-type='inline'] .CodeMirror-cursor {
+  :host([data-type='inline']) .CodeMirror-cursor {
     border-left: var(--jp-code-cursor-width1) solid
       var(--jp-editor-cursor-color);
   }
@@ -50,7 +50,7 @@
 
 /* When zoomed out less than 33% */
 @media screen and (min-width: 4320px) {
-  .jp-CodeMirrorEditor[data-type='inline'] .CodeMirror-cursor {
+  :host([data-type='inline']) .CodeMirror-cursor {
     border-left: var(--jp-code-cursor-width2) solid
       var(--jp-editor-cursor-color);
   }
@@ -113,116 +113,3 @@
 .jp-CodeMirror-ruler {
   border-left: 1px dashed var(--jp-border-color2);
 }
-
-/**
- * Here is our jupyter theme for CodeMirror syntax highlighting
- * This is used in our marked.js syntax highlighting and CodeMirror itself
- * The string "jupyter" is set in ../codemirror/widget.DEFAULT_CODEMIRROR_THEME
- * This came from the classic notebook, which came form highlight.js/GitHub
- */
-
-/**
- * CodeMirror themes are handling the background/color in this way. This works
- * fine for CodeMirror editors outside the notebook, but the notebook styles
- * these things differently.
- */
-.CodeMirror.cm-s-jupyter {
-  background: var(--jp-layout-color0);
-  color: var(--jp-content-font-color1);
-}
-
-/* In the notebook, we want this styling to be handled by its container */
-.jp-CodeConsole .CodeMirror.cm-s-jupyter,
-.jp-Notebook .CodeMirror.cm-s-jupyter {
-  background: transparent;
-}
-
-.cm-s-jupyter .CodeMirror-cursor {
-  border-left: var(--jp-code-cursor-width0) solid var(--jp-editor-cursor-color);
-}
-.cm-s-jupyter span.cm-keyword {
-  color: var(--jp-mirror-editor-keyword-color);
-  font-weight: bold;
-}
-.cm-s-jupyter span.cm-atom {
-  color: var(--jp-mirror-editor-atom-color);
-}
-.cm-s-jupyter span.cm-number {
-  color: var(--jp-mirror-editor-number-color);
-}
-.cm-s-jupyter span.cm-def {
-  color: var(--jp-mirror-editor-def-color);
-}
-.cm-s-jupyter span.cm-variable {
-  color: var(--jp-mirror-editor-variable-color);
-}
-.cm-s-jupyter span.cm-variable-2 {
-  color: var(--jp-mirror-editor-variable-2-color);
-}
-.cm-s-jupyter span.cm-variable-3 {
-  color: var(--jp-mirror-editor-variable-3-color);
-}
-.cm-s-jupyter span.cm-punctuation {
-  color: var(--jp-mirror-editor-punctuation-color);
-}
-.cm-s-jupyter span.cm-property {
-  color: var(--jp-mirror-editor-property-color);
-}
-.cm-s-jupyter span.cm-operator {
-  color: var(--jp-mirror-editor-operator-color);
-  font-weight: bold;
-}
-.cm-s-jupyter span.cm-comment {
-  color: var(--jp-mirror-editor-comment-color);
-  font-style: italic;
-}
-.cm-s-jupyter span.cm-string {
-  color: var(--jp-mirror-editor-string-color);
-}
-.cm-s-jupyter span.cm-string-2 {
-  color: var(--jp-mirror-editor-string-2-color);
-}
-.cm-s-jupyter span.cm-meta {
-  color: var(--jp-mirror-editor-meta-color);
-}
-.cm-s-jupyter span.cm-qualifier {
-  color: var(--jp-mirror-editor-qualifier-color);
-}
-.cm-s-jupyter span.cm-builtin {
-  color: var(--jp-mirror-editor-builtin-color);
-}
-.cm-s-jupyter span.cm-bracket {
-  color: var(--jp-mirror-editor-bracket-color);
-}
-.cm-s-jupyter span.cm-tag {
-  color: var(--jp-mirror-editor-tag-color);
-}
-.cm-s-jupyter span.cm-attribute {
-  color: var(--jp-mirror-editor-attribute-color);
-}
-.cm-s-jupyter span.cm-header {
-  color: var(--jp-mirror-editor-header-color);
-}
-.cm-s-jupyter span.cm-quote {
-  color: var(--jp-mirror-editor-quote-color);
-}
-.cm-s-jupyter span.cm-link {
-  color: var(--jp-mirror-editor-link-color);
-}
-.cm-s-jupyter span.cm-error {
-  color: var(--jp-mirror-editor-error-color);
-}
-.cm-s-jupyter span.cm-hr {
-  color: #999;
-}
-
-.cm-s-jupyter span.cm-tab {
-  background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAMCAYAAAAkuj5RAAAAAXNSR0IArs4c6QAAAGFJREFUSMft1LsRQFAQheHPowAKoACx3IgEKtaEHujDjORSgWTH/ZOdnZOcM/sgk/kFFWY0qV8foQwS4MKBCS3qR6ixBJvElOobYAtivseIE120FaowJPN75GMu8j/LfMwNjh4HUpwg4LUAAAAASUVORK5CYII=);
-  background-position: right;
-  background-repeat: no-repeat;
-}
-
-.cm-s-jupyter .CodeMirror-activeline-background,
-.cm-s-jupyter .CodeMirror-gutter {
-  background-color: var(--jp-layout-color2);
-}

+ 0 - 5
packages/codemirror/style/index.css

@@ -8,8 +8,3 @@
 @import url('~@jupyterlab/apputils/style/index.css');
 @import url('~@jupyterlab/codeeditor/style/index.css');
 @import url('~@jupyterlab/statusbar/style/index.css');
-@import url('~codemirror/lib/codemirror.css');
-@import url('~codemirror/addon/dialog/dialog.css');
-@import url('~codemirror/addon/fold/foldgutter.css');
-
-@import url('./base.css');

+ 110 - 0
packages/codemirror/style/jupyter-theme.css

@@ -0,0 +1,110 @@
+/*-----------------------------------------------------------------------------
+| Copyright (c) Jupyter Development Team.
+| Distributed under the terms of the Modified BSD License.
+|----------------------------------------------------------------------------*/
+
+/**
+ * Here is our jupyter theme for CodeMirror syntax highlighting
+ * This is used in our marked.js syntax highlighting and CodeMirror itself
+ * The string "jupyter" is set in ../codemirror/widget.DEFAULT_CODEMIRROR_THEME
+ * This came from the classic notebook, which came form highlight.js/GitHub
+ */
+
+:host {
+  background: var(--jp-layout-color0);
+  color: var(--jp-content-font-color1);
+}
+
+.CodeMirror {
+  background: transparent;
+}
+
+.cm-s-jupyter .CodeMirror-cursor {
+  border-left: var(--jp-code-cursor-width0) solid var(--jp-editor-cursor-color);
+}
+.cm-s-jupyter span.cm-keyword {
+  color: var(--jp-mirror-editor-keyword-color);
+  font-weight: bold;
+}
+.cm-s-jupyter span.cm-atom {
+  color: var(--jp-mirror-editor-atom-color);
+}
+.cm-s-jupyter span.cm-number {
+  color: var(--jp-mirror-editor-number-color);
+}
+.cm-s-jupyter span.cm-def {
+  color: var(--jp-mirror-editor-def-color);
+}
+.cm-s-jupyter span.cm-variable {
+  color: var(--jp-mirror-editor-variable-color);
+}
+.cm-s-jupyter span.cm-variable-2 {
+  color: var(--jp-mirror-editor-variable-2-color);
+}
+.cm-s-jupyter span.cm-variable-3 {
+  color: var(--jp-mirror-editor-variable-3-color);
+}
+.cm-s-jupyter span.cm-punctuation {
+  color: var(--jp-mirror-editor-punctuation-color);
+}
+.cm-s-jupyter span.cm-property {
+  color: var(--jp-mirror-editor-property-color);
+}
+.cm-s-jupyter span.cm-operator {
+  color: var(--jp-mirror-editor-operator-color);
+  font-weight: bold;
+}
+.cm-s-jupyter span.cm-comment {
+  color: var(--jp-mirror-editor-comment-color);
+  font-style: italic;
+}
+.cm-s-jupyter span.cm-string {
+  color: var(--jp-mirror-editor-string-color);
+}
+.cm-s-jupyter span.cm-string-2 {
+  color: var(--jp-mirror-editor-string-2-color);
+}
+.cm-s-jupyter span.cm-meta {
+  color: var(--jp-mirror-editor-meta-color);
+}
+.cm-s-jupyter span.cm-qualifier {
+  color: var(--jp-mirror-editor-qualifier-color);
+}
+.cm-s-jupyter span.cm-builtin {
+  color: var(--jp-mirror-editor-builtin-color);
+}
+.cm-s-jupyter span.cm-bracket {
+  color: var(--jp-mirror-editor-bracket-color);
+}
+.cm-s-jupyter span.cm-tag {
+  color: var(--jp-mirror-editor-tag-color);
+}
+.cm-s-jupyter span.cm-attribute {
+  color: var(--jp-mirror-editor-attribute-color);
+}
+.cm-s-jupyter span.cm-header {
+  color: var(--jp-mirror-editor-header-color);
+}
+.cm-s-jupyter span.cm-quote {
+  color: var(--jp-mirror-editor-quote-color);
+}
+.cm-s-jupyter span.cm-link {
+  color: var(--jp-mirror-editor-link-color);
+}
+.cm-s-jupyter span.cm-error {
+  color: var(--jp-mirror-editor-error-color);
+}
+.cm-s-jupyter span.cm-hr {
+  color: #999;
+}
+
+.cm-s-jupyter span.cm-tab {
+  background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAMCAYAAAAkuj5RAAAAAXNSR0IArs4c6QAAAGFJREFUSMft1LsRQFAQheHPowAKoACx3IgEKtaEHujDjORSgWTH/ZOdnZOcM/sgk/kFFWY0qV8foQwS4MKBCS3qR6ixBJvElOobYAtivseIE120FaowJPN75GMu8j/LfMwNjh4HUpwg4LUAAAAASUVORK5CYII=);
+  background-position: right;
+  background-repeat: no-repeat;
+}
+
+.cm-s-jupyter .CodeMirror-activeline-background,
+.cm-s-jupyter .CodeMirror-gutter {
+  background-color: var(--jp-layout-color2);
+}

+ 10 - 0
packages/codemirror/style/shadow.css

@@ -0,0 +1,10 @@
+/*-----------------------------------------------------------------------------
+| Copyright (c) Jupyter Development Team.
+| Distributed under the terms of the Modified BSD License.
+|----------------------------------------------------------------------------*/
+
+@import url('~codemirror/lib/codemirror.css');
+@import url('~codemirror/addon/dialog/dialog.css');
+@import url('~codemirror/addon/fold/foldgutter.css');
+
+@import url('./base.css');

+ 3 - 3
packages/codemirror/test/editor.spec.ts

@@ -280,16 +280,16 @@ describe('CodeMirrorEditor', () => {
   describe('#handleEvent', () => {
     describe('focus', () => {
       it('should add the focus class to the host', () => {
-        simulate(editor.editor.getInputField(), 'focus');
+        simulate(editor.editor.getInputField(), 'focus', { composed: true });
         expect(host.classList.contains('jp-mod-focused')).toBe(true);
       });
     });
 
     describe('blur', () => {
       it('should remove the focus class from the host', () => {
-        simulate(editor.editor.getInputField(), 'focus');
+        simulate(editor.editor.getInputField(), 'focus', { composed: true });
         expect(host.classList.contains('jp-mod-focused')).toBe(true);
-        simulate(editor.editor.getInputField(), 'blur');
+        simulate(editor.editor.getInputField(), 'blur', { composed: true });
         expect(host.classList.contains('jp-mod-focused')).toBe(false);
       });
     });

+ 2 - 0
testutils/src/jest-config.ts

@@ -4,6 +4,8 @@ module.exports = function(baseDir: string) {
   return {
     preset: 'ts-jest/presets/js-with-babel',
     moduleNameMapper: {
+      '^!!to-string-loader!css-loader!.+\\.css':
+        '@jupyterlab/testutils/lib/jest-style-mock.js',
       '\\.(css|less|sass|scss)$': 'identity-obj-proxy',
       '\\.(gif|ttf|eot)$': '@jupyterlab/testutils/lib/jest-file-mock.js'
     },

+ 1 - 0
testutils/src/jest-style-mock.ts

@@ -0,0 +1 @@
+module.exports = '';

+ 16 - 0
yarn.lock

@@ -10391,6 +10391,15 @@ loader-utils@^2.0.0:
     emojis-list "^3.0.0"
     json5 "^2.1.2"
 
+loader-utils@^1.0.0:
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613"
+  integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==
+  dependencies:
+    big.js "^5.2.2"
+    emojis-list "^3.0.0"
+    json5 "^1.0.1"
+
 locate-path@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e"
@@ -15047,6 +15056,13 @@ to-regex@^3.0.1, to-regex@^3.0.2:
     regex-not "^1.0.2"
     safe-regex "^1.1.0"
 
+to-string-loader@^1.1.6:
+  version "1.1.6"
+  resolved "https://registry.yarnpkg.com/to-string-loader/-/to-string-loader-1.1.6.tgz#230529ccc63dd0ecca052a85e1fb82afe946b0ab"
+  integrity sha512-VNg62//PS1WfNwrK3n7t6wtK5Vdtx/qeYLLEioW46VMlYUwAYT6wnfB+OwS2FMTCalIHu0tk79D3RXX8ttmZTQ==
+  dependencies:
+    loader-utils "^1.0.0"
+
 toggle-selection@^1.0.6:
   version "1.0.6"
   resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32"