PageSourceSearch

https://www.siemens.com/shared-assets/495634536ede41d22fb280180f78687b36cc3941/js/is-land.js

js siemens.com collected 2026-09-24 08:23:56 UTC 8,465 bytes, 337 lines download raw bytes

1const islandOnceCache = new Map();
2
3class Island extends HTMLElement {
4  static tagName = "is-land";
5
6  static fallback = {
7    ":not(:defined)": (readyPromise, node, prefix) => {
8      // remove from document to prevent web component init
9      let cloned = document.createElement(prefix + node.localName);
10      for(let attr of node.getAttributeNames()) {
11        cloned.setAttribute(attr, node.getAttribute(attr));
12      }
13
14      let children = Array.from(node.childNodes);
15      for(let child of children) {
16        cloned.append(child); // Keep the *same* child nodes, clicking on a details->summary child should keep the state of that child
17      }
18      node.replaceWith(cloned);
19
20      return readyPromise.then(() => {
21        // restore children (not cloned)
22        for(let child of Array.from(cloned.childNodes)) {
23          node.append(child);
24        }
25        cloned.replaceWith(node);
26      });
27    }
28  };
29
30  static autoinit = {
31    "petite-vue": function(library) {
32      library.createApp().mount(this);
33    },
34    "vue": function(library) {
35      library.createApp().mount(this);
36    },
37    "svelte": function(mod) {
38      new mod.default({ target: this });
39    },
40    "svelte-ssr": function(mod) {
41      new mod.default({ target: this, hydrate: true });
42    },
43    "preact": function(mod) {
44      mod.default(this);
45    }
46  }
47
48  constructor() {
49    super();
50
51    this.attrs = {
52      autoInitType: "autoinit",
53      import: "import",
54      template: "data-island",
55      ready: "ready",
56    };
57
58    this.conditionMap = {
59      visible: Conditions.visible,
60      idle: Conditions.idle,
61      interaction: Conditions.interaction,
62      media: Conditions.media,
63      "save-data": Conditions.saveData,
64    };
65
66    // Internal promises
67    this.ready = new Promise((resolve, reject) => {
68      this.readyResolve = resolve;
69      this.readyReject = reject;
70    });
71  }
72
73  static getParents(el, selector, stopAt = false) {
74    let nodes = [];
75    while(el) {
76      if(el.matches && el.matches(selector)) {
77        if(stopAt && el === stopAt) {
78          break;
79        }
80
81        nodes.push(el);
82      }
83      el = el.parentNode;
84    }
85    return nodes;
86  }
87
88  static async ready(el) {
89    let parents = Island.getParents(el, Island.tagName);
90    let imports = await Promise.all(parents.map(el => el.wait()));
91
92    // return innermost module import
93    if(imports.length) {
94      return imports[0];
95    }
96  }
97
98  async forceFallback() {
99    let prefix = Island.tagName + "--";
100    let promises = [];
101
102    if(window.Island) {
103      Object.assign(Island.fallback, window.Island.fallback);
104    }
105
106    for(let selector in Island.fallback) {
107      // Reverse here as a cheap way to get the deepest nodes first
108      let components = Array.from(this.querySelectorAll(selector)).reverse();
109
110      // with thanks to https://gist.github.com/cowboy/938767
111      for(let node of components) {
112        if(!node.isConnected || node.localName === Island.tagName) {
113          continue;
114        }
115
116        let readyPromise = Island.ready(node);
117        promises.push(Island.fallback[selector](readyPromise, node, prefix));
118      }
119    }
120
121    return promises;
122  }
123
124  wait() {
125    return this.ready;
126  }
127
128  getConditions() {
129    let map = {};
130    for(let key of Object.keys(this.conditionMap)) {
131      if(this.hasAttribute(`on:${key}`)) {
132        map[key] = this.getAttribute(`on:${key}`);
133      }
134    }
135
136    return map;
137  }
138
139  async connectedCallback() {
140    // Keep fallback content without initializing the components
141    // TODO improvement: only run this for not-eager components?
142    await this.forceFallback();
143
144    await this.hydrate();
145  }
146
147  getTemplates() {
148    return this.querySelectorAll(`:scope template[${this.attrs.template}]`);
149  }
150
151  replaceTemplates(templates) {
152    // replace <template> with the live content
153    for(let node of templates) {
154      if(Island.getParents(node, Island.tagName, this).length > 0) {
155        continue;
156      }
157      let value = node.getAttribute(this.attrs.template);
158      // get rid of the rest of the content on the island
159      if(value === "replace") {
160        let children = Array.from(this.childNodes);
161        for(let child of children) {
162          this.removeChild(child);
163        }
164        this.appendChild(node.content);
165        break;
166      } else {
167        if(value === "once" && node.innerHTML) {
168          if(islandOnceCache.has(node.innerHTML)) {
169            node.remove();
170            return;
171          }
172
173          islandOnceCache.set(node.innerHTML, true);
174        }
175
176        node.replaceWith(node.content);
177      }
178    }
179  }
180
181  async hydrate() {
182    let conditions = [];
183    if(this.parentNode) {
184      // wait for all parents before hydrating
185      conditions.push(Island.ready(this.parentNode));
186    }
187
188    let attrs = this.getConditions();
189    for(let condition in attrs) {
190      if(this.conditionMap[condition]) {
191        conditions.push(this.conditionMap[condition](attrs[condition], this));
192      }
193    }
194
195    // Loading conditions must finish before dependencies are loaded
196    await Promise.all(conditions);
197
198    this.replaceTemplates(this.getTemplates());
199
200    let mod;
201    // [dependency="my-component-code.js"]
202    let importScript = this.getAttribute(this.attrs.import);
203    if(importScript) {
204      // we could resolve import maps here manually but you’d still have to use the full URL in your script’s import anyway
205      mod = await import(importScript);
206    }
207
208    if(mod) {
209      // Use `import=""` for when import maps are available e.g. `import="petite-vue"`
210      let fn = Island.autoinit[this.getAttribute(this.attrs.autoInitType) || importScript];
211
212      if(fn) {
213        await fn.call(this, mod);
214      }
215    }
216
217    this.readyResolve({
218      import: mod
219    });
220
221    this.setAttribute(this.attrs.ready, "");
222  }
223}
224
225class Conditions {
226  static visible(noop, el) {
227    if(!('IntersectionObserver' in window)) {
228      // runs immediately
229      return;
230    }
231
232    return new Promise(resolve => {
233      let observer = new IntersectionObserver(entries => {
234        let [entry] = entries;
235        if(entry.isIntersecting) {
236          observer.unobserve(entry.target);
237          resolve();
238        }
239      });
240
241      observer.observe(el);
242    });
243  }
244
245  // TODO make sure this runs after all of the conditions have finished, otherwise it will
246  // finish way before the other lazy loaded promises and will be the same as a noop when
247  // on:interaction or on:visible finishes much later
248  static idle() {
249    let onload = new Promise(resolve => {
250      if(document.readyState !== "complete") {
251        window.addEventListener("load", () => resolve(), { once: true });
252      } else {
253        resolve();
254      }
255    });
256
257    if(!("requestIdleCallback" in window)) {
258      // run immediately
259      return onload;
260    }
261
262    // both idle and onload
263    return Promise.all([
264      new Promise(resolve => {
265        requestIdleCallback(() => {
266          resolve();
267        });
268      }),
269      onload,
270    ]);
271  }
272
273  static interaction(eventOverrides, el) {
274    let events = ["click", "touchstart"];
275    // event overrides e.g. on:interaction="mouseenter"
276    if(eventOverrides) {
277      events = (eventOverrides || "").split(",").map(entry => entry.trim());
278    }
279
280    return new Promise(resolve => {
281      function resolveFn(e) {
282        resolve();
283
284        // cleanup the other event handlers
285        for(let name of events) {
286          el.removeEventListener(name, resolveFn);
287        }
288      }
289
290      for(let name of events) {
291        el.addEventListener(name, resolveFn, { once: true });
292      }
293    });
294  }
295
296  static media(query) {
297    let mm = {
298      matches: true
299    };
300
301    if(query && ("matchMedia" in window)) {
302      mm = window.matchMedia(query);
303    }
304
305    if(mm.matches) {
306      return;
307    }
308
309    return new Promise(resolve => {
310      mm.addListener(e => {
311        if(e.matches) {
312          resolve();
313        }
314      });
315    });
316  }
317
318  static saveData(expects) {
319    // return early if API does not exist
320    if(!("connection" in navigator) || navigator.connection.saveData === (expects !== "false")) {
321      return Promise.resolve();
322    }
323
324    // dangly promise
325    return new Promise(() => {});
326  }
327}
328
329// Should this auto define? Folks can redefine later using { component } export
330if("customElements" in window) {
331  window.customElements.define(Island.tagName, Island);
332  window.Island = Island;
333}
334
335export const component = Island;
336
337export const ready = Island.ready;

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.