From 9a638b18623a734e500a1d51971f952d1306bdd5 Mon Sep 17 00:00:00 2001
From: Christoph Reiter <reiter.christoph@gmail.com>
Date: Mon, 7 Feb 2022 12:39:24 +0100
Subject: [PATCH] Add getShadowRootDocument() helper

In case the scoped registry polyfill is loaded we need to create
elements inside of the shadowroot.

getShadowRootDocument() returns the polyfilled shadowroot, or in case
the polyfill is not loaded just return the global document, which works
in case there are no scoped registries.

In case we get something like https://github.com/open-wc/open-wc/pull/2389
this will be usefull. Also it makes IDE completion work + eslint.
---
 packages/app-shell/src/app-shell.js           | 10 ++--------
 packages/common/src/utils.js                  | 20 +++++++++++++++++++
 packages/common/test/unit.js                  | 12 ++++++++++-
 .../src/nextcloud-file-picker.js              |  4 ++--
 4 files changed, 35 insertions(+), 11 deletions(-)

diff --git a/packages/app-shell/src/app-shell.js b/packages/app-shell/src/app-shell.js
index fb573076..5dadf1ad 100644
--- a/packages/app-shell/src/app-shell.js
+++ b/packages/app-shell/src/app-shell.js
@@ -2,7 +2,7 @@ import {createInstance} from './i18n.js';
 import {html, css} from 'lit';
 import {ScopedElementsMixin} from '@open-wc/scoped-elements';
 import {LanguageSelect} from '@dbp-toolkit/language-select';
-import {Icon} from '@dbp-toolkit/common';
+import {Icon, getShadowRootDocument} from '@dbp-toolkit/common';
 import {AuthKeycloak} from '@dbp-toolkit/auth';
 import {AuthMenuButton} from './auth-menu-button.js';
 import {Notification} from '@dbp-toolkit/notification';
@@ -829,13 +829,7 @@ export class AppShell extends ScopedElementsMixin(DBPLitElement) {
             this.defineScopedElement(activity.element, customElements.get(activity.element));
         });
 
-        // In case of "Scoped Custom Element Registries" polyfill we create a scoped element
-        let elm;
-        if (this.shadowRoot.createElement !== undefined) {
-            elm = this.shadowRoot.createElement(activity.element);
-        } else {
-            elm = document.createElement(activity.element);
-        }
+        let elm = getShadowRootDocument(this).createElement(activity.element);
 
         this._onActivityAdded(elm);
         this._lastElm = elm;
diff --git a/packages/common/src/utils.js b/packages/common/src/utils.js
index a2a2deb3..7d4b7b9b 100644
--- a/packages/common/src/utils.js
+++ b/packages/common/src/utils.js
@@ -15,3 +15,23 @@ export const combineURLs = (baseURL, addedURL) => {
     }
     return new URL(addedURL.replace(/^\/+/, ''), baseURL).href;
 };
+
+/**
+ * Returns a Document like thing that can be used to create elements.
+ *
+ * It provides createElement()/createElementNS()/importNode().
+ * The Document type annotation, while not correct, is used here for simplicity.
+ *
+ * https://github.com/WICG/webcomponents/blob/gh-pages/proposals/Scoped-Custom-Element-Registries.md#scoped-element-creation-apis
+ *
+ * @param {HTMLElement} element
+ * @returns {Document|null}
+ */
+export function getShadowRootDocument(element) {
+    // In case the polyfill is loaded return the shadowRoot
+    // otherwise fall back to the global document
+    if (ShadowRoot.prototype.createElement !== undefined) {
+        return element.shadowRoot;
+    }
+    return document;
+}
diff --git a/packages/common/test/unit.js b/packages/common/test/unit.js
index 7bf4ac4d..64c44c27 100644
--- a/packages/common/test/unit.js
+++ b/packages/common/test/unit.js
@@ -1,7 +1,7 @@
 import {expect, assert} from '@esm-bundle/chai';
 import * as utils from '../utils';
 import * as styles from '../styles';
-import {combineURLs} from '../';
+import {combineURLs, getShadowRootDocument} from '../';
 import '../jsonld.js';
 
 suite('utils', () => {
@@ -34,6 +34,16 @@ suite('utils', () => {
         assert.isTrue(res);
     });
 
+    test('getShadowRootDocument', () => {
+        class SomeElement3 extends HTMLElement {}
+        let res = utils.defineCustomElement('test-some-element-3', SomeElement3);
+        assert.isTrue(res);
+        let elm = new SomeElement3();
+        elm.attachShadow({mode: 'open'});
+        let doc = getShadowRootDocument(elm);
+        assert.isFunction(doc.createElement);
+    });
+
     test('getAssetURL', () => {
         // Backwards compat
         assert.equal(new URL(utils.getAssetURL('foo/bar')).pathname, '/foo/bar');
diff --git a/packages/file-handling/src/nextcloud-file-picker.js b/packages/file-handling/src/nextcloud-file-picker.js
index 8736bc44..13610f68 100644
--- a/packages/file-handling/src/nextcloud-file-picker.js
+++ b/packages/file-handling/src/nextcloud-file-picker.js
@@ -2,7 +2,7 @@ import {createInstance} from './i18n';
 import {css, html} from 'lit';
 import {ScopedElementsMixin} from '@open-wc/scoped-elements';
 import DBPLitElement from '@dbp-toolkit/common/dbp-lit-element';
-import {Icon, MiniSpinner} from '@dbp-toolkit/common';
+import {Icon, MiniSpinner, getShadowRootDocument} from '@dbp-toolkit/common';
 import * as commonUtils from '@dbp-toolkit/common/utils';
 import * as commonStyles from '@dbp-toolkit/common/styles';
 import {createClient, parseXML} from 'webdav/web';
@@ -217,7 +217,7 @@ export class NextcloudFilePicker extends ScopedElementsMixin(DBPLitElement) {
                                 cell.getValue() === 'directory'
                                     ? `<${icon_tag} name="folder" class="nextcloud-picker-icon"></${icon_tag}>`
                                     : icon;
-                            let div = this.shadowRoot.createElement('div');
+                            let div = getShadowRootDocument(this).createElement('div');
                             div.innerHTML = html;
                             return div;
                         },
-- 
GitLab