u2ui 1.4.6 → 1.5.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.
@@ -6,7 +6,7 @@ u2-alert {
6
6
  display:block;
7
7
  padding: 1em;
8
8
  margin-block: 1em;
9
- border: var(--line-width,1px) solid var(--color);
9
+ border: var(--line-width,1px) solid var(--color, currentColor);
10
10
  background-color: var(--color-bg, Canvas);
11
11
  width:100%;
12
12
  width:-webkit-fill-available;
@@ -2,7 +2,7 @@ u2-calendar {
2
2
  overflow:auto;
3
3
  max-height:90vh;
4
4
  &::part(drop-target) {
5
- outline: 2px dashed var(--color, #2196f3);
5
+ outline: 2px dashed var(--color, currentColor);
6
6
  z-index: 1;
7
7
  }
8
8
  }
@@ -11,7 +11,7 @@ u2-calendaritem {
11
11
  cursor: move;
12
12
  }
13
13
  &:focus {
14
- outline: 2px dotted var(--color-text, #000);
14
+ outline: 2px dotted var(--color-text, CanvasText);
15
15
  }
16
16
  }
17
17
 
@@ -84,12 +84,12 @@ u2-input:where([type="stepper"]) {
84
84
 
85
85
  u2-input[type="file"] {
86
86
  html.u2DraggingFile & {
87
- outline: .4rem solid var(--color-light, #0cf);
87
+ outline: .4rem solid var(--color-light, color-mix(in srgb, currentColor, transparent 60%));
88
88
  background-color:#ff6;
89
89
  outline-offset: .2rem;
90
90
  }
91
91
  &.u2DragOver.u2DragOver {
92
- outline: .4rem solid var(--color, #0cf);
92
+ outline: .4rem solid var(--color, currentColor);
93
93
  outline-offset: .2rem;
94
94
  }
95
95
  }
@@ -7,7 +7,7 @@ u2-maintitlebar {
7
7
  width: env(titlebar-area-width, 100%);
8
8
  max-width:100%;
9
9
  background-color: var(--color, canvas);
10
- color: var(--color-bg);
10
+ color: var(--color-bg, Canvas);
11
11
  z-index: 1000;
12
12
  font-size:min(14px, 1rem);
13
13
 
@@ -9,19 +9,19 @@ u2-pagination nav {
9
9
 
10
10
  & a {
11
11
  display: block;
12
- padding: calc(var(--gap) / 2) var(--gap);
12
+ padding: calc(var(--gap, 1rem) / 2) var(--gap, 1rem);
13
13
  text-decoration: none;
14
14
  color: inherit;
15
15
  text-align: center;
16
16
  }
17
17
 
18
18
  & a:hover {
19
- background: var(--color-light);
19
+ background: var(--color-light, color-mix(in srgb, CanvasText, Canvas 88%));
20
20
  }
21
21
 
22
22
  & a[aria-current="page"] {
23
23
  font-weight: bold;
24
- background: var(--color);
24
+ background: var(--color, CanvasText);
25
25
  color: white;
26
26
  }
27
27
  }
package/el/rte/rte.css CHANGED
@@ -1,6 +1,6 @@
1
1
  u2-rte {
2
2
  display:block;
3
- border:1px solid var(--color-line, #ccc);
3
+ border:1px solid var(--color-line, color-mix(in srgb, currentColor, transparent 75%));
4
4
  }
5
5
 
6
6
  u2-rte > textarea {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "u2ui",
3
- "version": "1.4.6",
3
+ "version": "1.5.0",
4
4
  "description": "HTML Advanced: More Capabilities, Same Feel, Same Simplicity",
5
5
  "keywords": ["web-components","pattern-library","component-library","ui-toolkit","design-system"],
6
6
  "author": "Tobias Buschor",
package/u2/auto.js CHANGED
@@ -16,19 +16,12 @@ if (debug) { // top level await safari >= 15.1
16
16
  }
17
17
 
18
18
  import {importCss} from './utils.js';
19
- import {repos} from './u2.js';
20
-
21
- const projects = await repos();
19
+ import {enhance} from './enhance.js';
22
20
 
23
21
  let prio = 1;
24
22
  setTimeout(()=>prio = 2);
25
23
  setTimeout(()=>prio = 3, 2000);
26
24
  const needed = { js:{}, css:{} };
27
- function impJs(url){
28
- if (!url) return;
29
- needed.js[url] ??= prio;
30
- return import(url);
31
- }
32
25
  function impCss(url, options={}){
33
26
  if (!url || url in needed.css) return;
34
27
  importCss(url, options).then(res=>{
@@ -64,49 +57,9 @@ impCss(rootUrl+'css/classless/more.css');
64
57
  ////////////////////////////////////////////////////////////////
65
58
 
66
59
 
67
- function newNode(node){
68
- if (node.tagName.startsWith('U2-')) {
69
- let name = node.tagName.substring(3).toLowerCase();
70
- if (!customElements.get('u2-'+name)) {
71
- loadProject('el', name);
72
- }
73
- }
74
- for (const klass of node.classList) {
75
- if (!klass.startsWith('u2-')) continue;
76
- let name = klass.substring(3);
77
- loadProject('class', name);
78
- }
79
- for (const attr of node.attributes) {
80
- if (!attr.name.startsWith('u2-')) continue;
81
- let name = attr.name.substring(3).replace(/-.*/,''); // example: u2-href-target => href
82
- loadProject('attr', name);
83
- }
84
- }
85
-
86
- function loadProject(category, name){
87
- const id = category + '/' + name;
88
- const meta = projects[id];
89
- if (!meta) { console.warn('u2: project not found:', category+'/'+name); return; }
90
- const hasJs = meta.js ?? (category==='el' || category==='attr');
91
- const hasCss = meta.css ?? (category==='el' || category==='class');
92
- hasCss && impCss(rootUrl + id + '/' + name + '.css');
93
- hasJs && impJs(rootUrl + id + '/' + name + '.js');
94
- }
95
-
96
- function newNodeRoot(node){
97
- if (!node.tagName) return;
98
- newNode(node);
99
- node.querySelectorAll('*').forEach(newNode);
100
- }
101
- var mo = new MutationObserver((entries)=>{
102
- for (const entry of entries) {
103
- for (const node of entry.addedNodes) newNodeRoot(node);
104
- }
105
- });
106
-
60
+ /** enhance() does the scanning and loading; this file only records it for the ui. */
107
61
  export function addShadowRoot(rootNode){
108
- mo.observe(rootNode,{childList:true, subtree:true, characterData:false});
109
- newNodeRoot(rootNode);
62
+ return enhance(rootNode, { onLoad: (kind, url) => needed[kind][url] ??= prio });
110
63
  }
111
64
  addShadowRoot(document.documentElement)
112
65
 
package/u2/enhance.js ADDED
@@ -0,0 +1,145 @@
1
+ /* Run u2 inside one root: watch it, load what its markup needs and put it there — elements
2
+ in that root's own registry, css as adopted sheets. Unlike ./auto.js this touches nothing
3
+ outside the root: no global stylesheets, no window.customElements, no localStorage, no ui.
4
+
5
+ In a shadow root the css is an adopted sheet, parsed once and shared by every other root;
6
+ fetching it needs the same connect-src as ./u2.js already does for projects.json. In the
7
+ document it stays a <link>, so the page keeps its say over u2's own rules.
8
+
9
+ See ./plan-scoped-refactoring.md */
10
+
11
+ import { repos } from './u2.js';
12
+ import { importCss } from './utils.js';
13
+
14
+ const base = new URL('../', import.meta.url).href; // this copy of u2 — another root may run another
15
+ const scoped = 'customElementRegistry' in ShadowRoot.prototype;
16
+ // The default scope: module identity gives one per u2 version, so a caller who does not care
17
+ // gets isolation for free. A caller who brings its own wins, and the elements follow the root.
18
+ const versionRegistry = scoped ? new CustomElementRegistry() : undefined;
19
+ const sheets = new Map(); // url -> Promise<CSSStyleSheet>, shared across roots
20
+
21
+ // the manifest is wanted by every load, so fetch it on import and keep the resolved map
22
+ let projects = null;
23
+ repos().then(map => projects = map);
24
+
25
+ function sheet(url) {
26
+ if (!sheets.has(url)) {
27
+ sheets.set(url, fetch(url).then(r => r.text()).then(css => {
28
+ const s = new CSSStyleSheet();
29
+ s.replaceSync(css); // u2 css has no relative url() and no @import
30
+ return s;
31
+ }));
32
+ }
33
+ return sheets.get(url);
34
+ }
35
+
36
+ /** Attach a shadow root that runs its own u2. Same signature as the native method. */
37
+ export function attachShadow(host, options = {}) {
38
+ const registry = options.customElementRegistry ?? versionRegistry;
39
+ const root = host.attachShadow({ mode: 'open', ...options, customElementRegistry: registry });
40
+ enhance(root);
41
+ return root;
42
+ }
43
+
44
+ /** Watch a root and load the u2 parts its markup uses. Returns a function that stops watching.
45
+ * The registry is the root's own — define anywhere else and the root would not resolve it.
46
+ * `onLoad(kind, url)` reports every file, for callers that keep a list of what a page needs. */
47
+ export function enhance(root, { registry = root.customElementRegistry ?? customElements, onLoad } = {}) {
48
+ const seen = new Set();
49
+ const isShadow = root instanceof ShadowRoot;
50
+
51
+ // Adoption order is the cascade, so the fetch starts at once but the push waits its turn.
52
+ let queue = Promise.resolve();
53
+ function addCss(url) {
54
+ if (!isShadow) return importCss(url); // in the document a <link> keeps the page's say
55
+ const loading = sheet(url);
56
+ queue = queue.then(async () => root.adoptedStyleSheets.push(await loading));
57
+ }
58
+
59
+ // norm and base are what a root cannot inherit: box-sizing, font:inherit on form controls,
60
+ // images that stay inside. Not variables.css — tokens reach a root by inheritance.
61
+ for (const css of ['css/norm/norm.css', 'css/base/base.css']) {
62
+ const url = base + css;
63
+ addCss(url);
64
+ onLoad?.('css', url);
65
+ }
66
+
67
+ // The repeat case must stay synchronous: a list of 50 identical elements calls this 50
68
+ // times, and only the first may allocate. Nothing awaits it, so run() catches its own.
69
+ function load(category, name) {
70
+ const id = category + '/' + name;
71
+ if (seen.has(id)) return;
72
+ seen.add(id);
73
+ run(id, category, name).catch(err => console.warn('u2: could not load', id, err));
74
+ }
75
+
76
+ async function run(id, category, name) {
77
+ const meta = (projects ?? await repos())[id];
78
+ if (!meta) { console.warn('u2: project not found:', id); return; }
79
+
80
+ // css belongs in this root: a class or an attribute has no effect without it,
81
+ // and element css styles the element from outside its own shadow
82
+ if (meta.css ?? (category !== 'attr')) {
83
+ const url = `${base}${id}/${name}.css`;
84
+ // A shadow root shares one parsed sheet with every other root. In the document a
85
+ // <link> keeps the cascade as it is — an adopted sheet would outrank the page's
86
+ // own stylesheets and break overrides. (Layers may make this moot one day.)
87
+ addCss(url);
88
+ onLoad?.('css', url);
89
+ }
90
+ if (!(meta.js ?? (category !== 'class'))) return;
91
+
92
+ const jsUrl = `${base}${id}/${name}.js`;
93
+ const mod = await import(jsUrl);
94
+ onLoad?.('js', jsUrl);
95
+ // attributes register nothing — their listeners sit on the document and cross shadow
96
+ // boundaries on purpose, so they are not scoped by this root
97
+ if (category !== 'el') return;
98
+
99
+ const tag = 'u2-' + name;
100
+ // the file still registers itself globally — that is a different registry
101
+ if (!registry.get(tag)) registry.define(tag, mod.default);
102
+ for (const [t, cls] of Object.entries(mod.extraElements ?? {})) {
103
+ if (!registry.get(t)) registry.define(t, cls);
104
+ }
105
+ }
106
+
107
+ // Hot path: every element inserted into the root passes through here. Index loops, so
108
+ // neither a DOMTokenList nor a NamedNodeMap iterator is allocated per element.
109
+ function visit(el) {
110
+ const tag = el.tagName;
111
+ if (tag.startsWith('U2-')) load('el', tag.slice(3).toLowerCase());
112
+
113
+ const cls = el.className; // string on html elements, SVGAnimatedString on svg
114
+ if (typeof cls === 'string' && cls.includes('u2-')) {
115
+ const list = el.classList;
116
+ for (let i = 0, n = list.length; i < n; i++) {
117
+ const c = list[i];
118
+ if (c.startsWith('u2-')) load('class', c.slice(3));
119
+ }
120
+ }
121
+
122
+ const attrs = el.attributes;
123
+ for (let i = 0, n = attrs.length; i < n; i++) {
124
+ const name = attrs[i].name;
125
+ if (name.startsWith('u2-')) load('attr', name.slice(3).replace(/-.*/, ''));
126
+ }
127
+ }
128
+
129
+ function scan(node) {
130
+ if (node.nodeType === 1) visit(node); // text and comment nodes carry nothing
131
+ const els = node.querySelectorAll?.('*');
132
+ if (els) for (let i = 0, n = els.length; i < n; i++) visit(els[i]);
133
+ }
134
+
135
+ const observer = new MutationObserver(entries => {
136
+ for (let i = 0, n = entries.length; i < n; i++) {
137
+ const added = entries[i].addedNodes;
138
+ for (let j = 0, m = added.length; j < m; j++) scan(added[j]);
139
+ }
140
+ });
141
+ observer.observe(root, { childList: true, subtree: true });
142
+ scan(root);
143
+
144
+ return () => observer.disconnect();
145
+ }
@@ -131,20 +131,43 @@ sowohl U2Auto als auch der Datei-Side-Effect definieren können).
131
131
  - muss **vor** dem ersten scoped ShadowRoot geladen sein,
132
132
  - etwas Laufzeit-Overhead,
133
133
  - wenige Edge-Cases (`el.constructor`).
134
+ - **Namen (recherchiert, nicht `registry` — das war ein Entwurfsname):**
135
+ `host.attachShadow({ customElementRegistry })`, Rücklesen über
136
+ `root.customElementRegistry`, programmatisch
137
+ `document.createElement(tag, { customElementRegistry })`, nachträglich
138
+ `registry.initialize(root)`. Chrome/Edge 146+, Safari 26+, Firefox noch nicht.
134
139
  - Feature-Detection:
135
140
  ```js
136
- if (!('registry' in ShadowRoot.prototype))
141
+ if (!('customElementRegistry' in ShadowRoot.prototype))
137
142
  await import('.../scoped-custom-element-registry.min.js');
138
143
  ```
139
144
  Einmal am Anfang laden, danach funktioniert scoped überall gleich.
140
145
 
141
146
  ## CSS-Anmerkung
142
147
 
143
- Komponenten-CSS liegt bereits im jeweiligen Shadow der Elemente (z.B. accordion
144
- importiert `ico.css` in seinen eigenen ShadowRoot). Das globale `impCss` aus auto.js ist
145
- nur für classless/utilities/base zuständig. Für ein scoped Panel entscheidet man separat,
146
- ob und welche dieser globalen Stylesheets **in** das Panel-Shadow geladen werden — das ist
147
- unabhängig vom Element-Registry-Thema.
148
+ Komponenten-CSS liegt teils im Shadow des Elements (accordion importiert `ico.css` in sein
149
+ eigenes Shadow), teils stylt es das Element **von aussen**: `ico.css` setzt
150
+ `u2-ico { --size }` und `u2-ico > svg { height }`. Solches CSS muss in den
151
+ **konsumierenden** Root — ohne es hat `u2-ico` dort keine Grösse. `U2Auto` lädt Element-CSS
152
+ deshalb in seinen Root, nicht ins Dokument.
153
+
154
+ Das globale `impCss` (classless/utilities/base) bleibt davon unberührt; ob es zusätzlich ins
155
+ Panel-Shadow soll, entscheidet man separat.
156
+
157
+ **Was in einen Root gehört und was nicht** — die Trennlinie verläuft nach Selektor-Reichweite,
158
+ nicht nach Datei:
159
+
160
+ | | |
161
+ |---|---|
162
+ | vererbend (Tokens, `line-height`, `hyphens`, `font-family`) | kommt durch die Grenze — **nicht** in `:host` setzen, das wäre ein Zurücksetzen |
163
+ | Element-/Universalselektoren (`*{box-sizing}`, `input{font:inherit}`, `img{max-inline-size}`, `table`, `a`, `h1`) | hält an der Grenze — **nur dafür** adoptiert man ein Sheet |
164
+ | Host-Kasten (`block-size`, `overflow-y`, `scrollbar-gutter`) | gehört gar nicht auf `:host` |
165
+
166
+ `input, textarea, select, button { font: inherit }` ist der wichtigste: es *stellt* Vererbung
167
+ erst her, die UA-Styles brechen. Ohne diese Regel erbt ein Formularfeld im Root nichts.
168
+
169
+ Wer in einem Root wirklich neu beginnen will, setzt die Werte selbst — die Sheets tun es nicht
170
+ für ihn.
148
171
 
149
172
  ## Interne Element-Deps (der harte Teil)
150
173
 
@@ -152,24 +175,31 @@ Elemente, die andere u2-Elemente in ihr **eigenes** Shadow rendern, dürfen sich
152
175
  auf den globalen Side-Effect verlassen. In einer scoped Registry ist `u2-ico` sonst nicht
153
176
  sichtbar.
154
177
 
155
- **Umstellung** (registry-agnostisch, schon heute non-breaking):
178
+ **Gemessen** ([u2/tests/enhance.test.html](tests/enhance.test.html), Chrome nativ):
179
+
180
+ - Ein im scoped Root geparstes Element trägt die Registry: `el.customElementRegistry === outer`.
181
+ - Aber `el.attachShadow({mode})` **ohne** die Option bekommt `window.customElements` — die
182
+ Vererbung passiert *nicht* automatisch. **Isolation komponiert nicht von selbst.**
183
+ - Dieselbe Klasse darf in **mehreren** Registries definiert werden. Ein Element braucht darum
184
+ keine Singleton zu importieren, es folgt seinem Root.
185
+
186
+ Ein Element muss seine Registry deshalb aktiv weiterreichen, und zwar an beiden Stellen:
156
187
 
157
188
  ```js
158
- // vorher (Side-Effect)
159
- import('../ico/ico.js');
189
+ const registry = this.customElementRegistry ?? customElements;
190
+ this.attachShadow({ mode: 'open', customElementRegistry: registry });
160
191
 
161
- // nachher (explizit + Guard)
162
192
  import U2Ico from '../ico/ico.js';
163
- define('u2-ico', U2Ico);
193
+ registry.define('u2-ico', U2Ico); // nicht global
164
194
  ```
165
195
 
166
- Betroffen u.a.: `accordion`→`ico`, `rating`→`ico`, `buttongroup`→`focusgroup`,
167
- `fields`→`responsive`.
196
+ Betroffen: `accordion`→`ico`, `alert`→`ico`, `calendar`→`ico`, `rating`→`ico`,
197
+ `input`→`ico`+`bytes`, `fields`→`responsive`.
168
198
 
169
- > Vollständig sauber wird das erst, wenn solche Kind-Elemente in **dieselbe** scoped
170
- > Registry registriert werden wie das Eltern-Shadow. Für die erste Ausbaustufe (globale
171
- > Nutzung) reicht die explizite `define()`-Umstellung; die scoped-Verkettung ist ein
172
- > Folge-Schritt.
199
+ > Ohne diesen Schritt ist die Isolation genau eine Ebene tief: das Panel-Root benutzt seine
200
+ > Version, alles was ein Element in sein *eigenes* Shadow rendert, kommt aus der globalen —
201
+ > also aus der Version der Host-Seite. `enhance` kann das nicht beheben, sein
202
+ > MutationObserver endet an jeder Shadow-Grenze.
173
203
 
174
204
  ## Migrationsplan (phasenweise)
175
205
 
@@ -201,7 +231,9 @@ Nach Phase 1: global unverändert, aber die gesamte Plumbing für scoped ist vor
201
231
  6. **Polyfill-Loader** + Feature-Detection einbauen.
202
232
  7. **`attachU2Shadow(host)`** exportieren.
203
233
  8. CMS-Panel: eigenes Shadow via `attachU2Shadow` erzeugen und dort die isolierte
204
- u2-Instanz laufen lassen.
234
+ u2-Instanz laufen lassen. Scoped werden nur die `u2-*`-Elemente; die eigenen Elemente
235
+ des Konsumenten bleiben global — sie haben eindeutige Namen, existieren in einer
236
+ Version, und der Host muss von aussen auffindbar bleiben.
205
237
 
206
238
  ### Phase 3 — Isolation scharf schalten (der einzige echte Break)
207
239
 
@@ -231,6 +263,19 @@ Erst ab **Phase 3** relevant:
231
263
 
232
264
  ## Offen / später
233
265
 
234
- - **Scoped Attribute-Registry** (`u2-*`-Attribute isoliert): gleiche Factory-Idee wie
235
- `U2Auto`, aber eigener Mechanismus. Bewusst **später**, um den Scope hier klein zu halten.
266
+ - **`U2Element` als Basisklasse** (wenn sie kommt) nimmt den Elementen die Registry-Arbeit ab:
267
+ `attachShadow(options)` überschreiben und `customElementRegistry: this.customElementRegistry`
268
+ vorbelegen — dann bleiben alle Aufrufstellen wie heute. Dazu ein Einzeiler für Kind-Elemente,
269
+ etwa `this.registerElement('ico')`: importiert die Klasse, definiert sie in die eigene
270
+ Registry **und adoptiert `ico.css` ins eigene Shadow**. Beides steht heute von Hand in jedem
271
+ Modul, und `fields` vergisst die CSS von `responsive` bereits. Async ist unkritisch — ein
272
+ Element upgraded auch, wenn es erst nach dem `innerHTML` definiert wird.
273
+ Ein `createElement`-Helper lohnt nicht: `innerHTML` benutzt ohnehin die Registry des Shadows.
274
+ - **Attribute sind kein Registry-Problem.** Sie kollidieren nicht über Namen, sie
275
+ *installieren sich doppelt*, wenn zwei u2-Kopien laufen. Alle 10 hängen ihre Listener an
276
+ `document`, vier davon (`confirm`, `dropzone`, `draghandle`, `movable`) benutzen
277
+ `composedPath()` — sie arbeiten absichtlich über Shadow-Grenzen hinweg, und sie an einen
278
+ Scope zu binden würde sie kaputtmachen.
279
+ Zu klären ist deshalb nicht „wie scopen wir", sondern zweierlei: **einmal pro Dokument**
280
+ installieren, und **pro Modul** entscheiden, welche Wirkung wirklich subtree-lokal ist.
236
281
  - Scoped-Verkettung von Kind-Elementen in die Eltern-Registry (siehe „Interne Deps").
@@ -45,6 +45,8 @@ function generateSkillContent(repos) {
45
45
  return dedent(`
46
46
  # u2 Framework
47
47
 
48
+ <!-- generated by u2/tools/update.skill.js — edit the template there, not this file -->
49
+
48
50
  Modular web component framework.
49
51
  https://github.com/u2ui/u2
50
52
 
@@ -78,6 +80,8 @@ function generateSkillContent(repos) {
78
80
 
79
81
  ## CSS
80
82
 
83
+ Everything under \`css/\` is **optional** — wanting only \`u2-alert\` does not drag in \`base.css\`.
84
+
81
85
  auto.js also loads: css/base/base.css, css/classless/variables.css, css/classless/classless.css
82
86
 
83
87
  Sets sensible defaults — do not re-implement these when base is loaded:
package/u2/u2.js CHANGED
@@ -14,17 +14,17 @@ u2.el = function(elRepo, options){
14
14
 
15
15
 
16
16
  let cachedRepos = null;
17
- export async function repos(){
18
- if (!cachedRepos) {
19
- cachedRepos = {};
20
- const data = await fetch(import.meta.url + '/../projects.json').then(res=>res.json());
21
- for (const category in data) {
22
- for (const repo in data[category]) {
23
- cachedRepos[category+'/'+repo] = data[category][repo];
17
+ /** The promise is the cache — caching the object handed a still-empty one to concurrent callers. */
18
+ export function repos(){
19
+ return cachedRepos ??= fetch(import.meta.url + '/../projects.json')
20
+ .then(res => res.json())
21
+ .then(data => {
22
+ const flat = {};
23
+ for (const category in data) {
24
+ for (const repo in data[category]) flat[category+'/'+repo] = data[category][repo];
24
25
  }
25
- }
26
- }
27
- return cachedRepos;
26
+ return flat;
27
+ });
28
28
  }
29
29
 
30
30
 
package/u2/utils.js CHANGED
@@ -1,7 +1,7 @@
1
1
  ///////////////////////////
2
2
  // cssImport
3
3
 
4
- const cssImported = [];
4
+ const cssImported = Object.create(null); // keyed by url — an array here means dictionary mode
5
5
  export function importCss(url, options={}) {
6
6
  return new Promise((resolve, reject) => {
7
7
  const link = _importCss(url, options);