u2ui 1.5.1 → 1.5.3

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.
@@ -1,17 +1,15 @@
1
+ import U2Element from '../../u2/Element.js';
1
2
 
2
- const icoCssUrl = import.meta.resolve('../../el/ico/ico.css');
3
3
 
4
- export default class U2Accordion extends HTMLElement {
4
+ export default class U2Accordion extends U2Element {
5
5
 
6
6
  constructor() {
7
7
  super();
8
8
 
9
- import('../ico/ico.js');
10
-
11
9
  let shadowRoot = this.attachShadow({ mode: 'open', slotAssignment: 'manual' });
10
+ this.useEl('ico');
12
11
  shadowRoot.innerHTML = `
13
12
  <style>
14
- @import url('${icoCssUrl}');
15
13
  :host {
16
14
  display:block;
17
15
  gap:.5em;
package/el/alert/alert.js CHANGED
@@ -1,8 +1,6 @@
1
-
2
- const icoCssUrl = import.meta.resolve('../../el/ico/ico.css');
1
+ import U2Element from '../../u2/Element.js';
3
2
 
4
3
  const style = `
5
- @import url('${icoCssUrl}');
6
4
  :host(:not([dismissable]))::part(close) { display:none; } /* TODO: does not work in chrome */
7
5
  :host {
8
6
  --u2-ico-dir:var(--u2-ico-dir-material);
@@ -80,14 +78,13 @@ slot[name=icon] {
80
78
  /* slot[name=action]:empty-slot { display:none; } not possible */`;
81
79
 
82
80
 
83
- export default class U2Alert extends HTMLElement {
81
+ export default class U2Alert extends U2Element {
84
82
  constructor() {
85
83
  super();
86
84
 
87
- import('../../attr/focusgroup/focusgroup.js');
88
- import('../ico/ico.js');
89
-
90
85
  this.attachShadow({mode: 'open'});
86
+ this.useEl('ico');
87
+ this.useAttr('focusgroup');
91
88
  this.shadowRoot.innerHTML = `
92
89
  <style>${style}</style>
93
90
  <div id=container>
@@ -1,5 +1,6 @@
1
+ import U2Element from '../../u2/Element.js';
1
2
 
2
- export default class U2Calendar extends HTMLElement {
3
+ export default class U2Calendar extends U2Element {
3
4
  static observedAttributes = ['view', 'date', 'lang'];
4
5
 
5
6
  constructor() {
@@ -8,12 +9,10 @@ export default class U2Calendar extends HTMLElement {
8
9
  this._currentDate = new Date();
9
10
  this._locale = null;
10
11
  this._weekStart = 0; // 0 = Sunday .. 6 = Saturday
11
- import('../ico/ico.js');
12
- const icoCssUrl = import.meta.resolve('../ico/ico.css');
12
+ this.useEl('ico');
13
13
 
14
14
  this.shadowRoot.innerHTML = `
15
15
  <style>
16
- @import url('${icoCssUrl}');
17
16
  :host {
18
17
  display: flex;
19
18
  flex-direction: column;
@@ -295,7 +294,7 @@ export default class U2Calendar extends HTMLElement {
295
294
 
296
295
 
297
296
 
298
- class U2CalendarItem extends HTMLElement {
297
+ class U2CalendarItem extends U2Element {
299
298
  static observedAttributes = ['start', 'end'];
300
299
 
301
300
  constructor() {
@@ -1,8 +1,9 @@
1
- export default class U2Fields extends HTMLElement {
1
+ import U2Element from '../../u2/Element.js';
2
+ export default class U2Fields extends U2Element {
2
3
  constructor() {
3
4
  super();
4
5
  this.attachShadow({ mode: 'open', slotAssignment: 'manual' });
5
- import('../responsive/responsive.js');
6
+ this.useEl('responsive');
6
7
 
7
8
  this.shadowRoot.innerHTML = `
8
9
  <style>
@@ -11,7 +12,7 @@ export default class U2Fields extends HTMLElement {
11
12
  margin-block:.5rem;
12
13
  white-space:nowrap;
13
14
  }
14
- u2-responsive {
15
+ #container { /* out-specify responsive.css, which useEl now adopts */
15
16
  display: grid;
16
17
  align-items:baseline;
17
18
  grid-template-columns: auto 1fr;
package/el/input/input.js CHANGED
@@ -1,3 +1,4 @@
1
+ import U2Element from '../../u2/Element.js';
1
2
  // Type-Handler mit Lifecycle
2
3
  class TypeHandler {
3
4
  constructor(element, shadow) {
@@ -288,7 +289,6 @@ class FileHandler extends TypeHandler {
288
289
  }
289
290
  get css() {
290
291
  return `
291
- @import url('${import.meta.resolve('../../class/badge/badge.css')}');
292
292
  :host { vertical-align: top; }
293
293
  #droparea {
294
294
  flex: 1 1 auto;
@@ -334,7 +334,8 @@ class FileHandler extends TypeHandler {
334
334
  }
335
335
 
336
336
  init() {
337
- import('../bytes/bytes.js');
337
+ this.element.useEl('bytes');
338
+ this.element.useClass('badge');
338
339
 
339
340
  const browse = this.shadow.getElementById('browse');
340
341
 
@@ -433,15 +434,13 @@ const TYPE_REGISTRY = {
433
434
  };
434
435
 
435
436
  // Custom Element
436
- export default class U2Input extends HTMLElement {
437
+ export default class U2Input extends U2Element {
437
438
  constructor() {
438
439
  super();
439
- import('../ico/ico.js');
440
-
441
440
  const shadow = this.attachShadow({ mode: 'open', delegatesFocus: true });
441
+ this.useEl('ico');
442
442
  shadow.innerHTML = `
443
443
  <style>
444
- @import url('${import.meta.resolve('../ico/ico.css')}');
445
444
 
446
445
  :host {
447
446
  display: inline-block;
@@ -1,7 +1,6 @@
1
- const icoCssUrl = import.meta.resolve('../../el/ico/ico.css');
1
+ import U2Element from '../../u2/Element.js';
2
2
 
3
3
  const style = `
4
- @import url('${icoCssUrl}');
5
4
  :host {
6
5
  --u2-ico-dir: var(--u2-ico-dir-material);
7
6
  --color-inactive:#ccc;
@@ -25,15 +24,14 @@ const style = `
25
24
  }
26
25
  `;
27
26
 
28
- export default class U2Rating extends HTMLElement {
27
+ export default class U2Rating extends U2Element {
29
28
  static formAssociated = true;
30
29
  #value = null;
31
30
  constructor() {
32
31
  super();
33
32
 
34
- import('../ico/ico.js');
35
-
36
33
  this.attachShadow({mode: 'open'});
34
+ this.useEl('ico');
37
35
  this.shadowRoot.innerHTML = `
38
36
  <style>${style}</style>
39
37
  <div id=stars></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "u2ui",
3
- "version": "1.5.1",
3
+ "version": "1.5.3",
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/Element.js ADDED
@@ -0,0 +1,49 @@
1
+ const base = new URL('../', import.meta.url).href; // this copy of u2
2
+
3
+ /** Base class for u2 elements — keeps a subtree on one u2 version.
4
+ A scoped shadow root only resolves tags its own registry knows, and a shadow opened
5
+ without the option silently falls back to the global one. Inheriting it instead means
6
+ nesting composes: whatever version enhanced the root stays the version all the way down. */
7
+ export default class U2Element extends HTMLElement {
8
+
9
+ attachShadow(options) {
10
+ return super.attachShadow({customElementRegistry: this.customElementRegistry, ...options});
11
+ }
12
+
13
+ useEl(name, options) { return use(this, 'el', name, options); }
14
+ useClass(name, options) { return use(this, 'class', name, options); }
15
+ useAttr(name, options) { return use(this, 'attr', name, options); }
16
+ }
17
+
18
+ /** Bring what a child needs into this element's shadow. Both start at once — the css is the
19
+ part that shows, it must not wait for a module. Defaults follow the category: an el has
20
+ both, a class is css only, an attr is js only — `u2-skin` is the attr that styles, and it
21
+ stays an attr because it carries a value: useAttr('skin', {css:true}). */
22
+ function use(el, category, name, {css = category !== 'attr', js = category !== 'class'} = {}) {
23
+ const path = `${base}${category}/${name}/${name}`;
24
+ const warn = err => console.warn('u2: could not load', category + '/' + name, err);
25
+ // unshift, not push: the element's own styles are adopted after these and have to keep
26
+ // winning ties, no matter when the child's sheet arrives.
27
+ if (css) sheet(path + '.css').then(s => el.shadowRoot.adoptedStyleSheets.unshift(s), warn);
28
+ if (!js) return;
29
+ return import(path + '.js').then(mod => {
30
+ if (category !== 'el') return; // a class has no js, an attr installs itself
31
+ const registry = el.customElementRegistry ?? customElements;
32
+ const tag = 'u2-' + name;
33
+ registry.get(tag) || registry.define(tag, mod.default);
34
+ }, warn);
35
+ }
36
+
37
+
38
+ const sheets = new Map();
39
+ /** One parsed CSSStyleSheet per url, shared by every root that adopts it — enhance uses it too. */
40
+ export function sheet(url) {
41
+ if (!sheets.has(url)) {
42
+ sheets.set(url, fetch(url).then(r => r.text()).then(css => {
43
+ const s = new CSSStyleSheet();
44
+ s.replaceSync(css); // u2 css has no relative url() and no @import
45
+ return s;
46
+ }));
47
+ }
48
+ return sheets.get(url);
49
+ }
package/u2/enhance.js CHANGED
@@ -10,28 +10,18 @@ See ./plan-scoped-refactoring.md */
10
10
 
11
11
  import { repos } from './u2.js';
12
12
  import { importCss } from './utils.js';
13
+ import { sheet } from './Element.js';
13
14
 
14
15
  const base = new URL('../', import.meta.url).href; // this copy of u2 — another root may run another
15
16
  const scoped = 'customElementRegistry' in ShadowRoot.prototype;
16
17
  // The default scope: module identity gives one per u2 version, so a caller who does not care
17
18
  // gets isolation for free. A caller who brings its own wins, and the elements follow the root.
18
19
  const versionRegistry = scoped ? new CustomElementRegistry() : undefined;
19
- const sheets = new Map(); // url -> Promise<CSSStyleSheet>, shared across roots
20
20
 
21
21
  // the manifest is wanted by every load, so fetch it on import and keep the resolved map
22
22
  let projects = null;
23
23
  repos().then(map => projects = map);
24
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
25
 
36
26
  /** Attach a shadow root that runs its own u2. Same signature as the native method. */
37
27
  export function attachShadow(host, options = {}) {
@@ -263,14 +263,6 @@ Erst ab **Phase 3** relevant:
263
263
 
264
264
  ## Offen / später
265
265
 
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
266
  - **Attribute sind kein Registry-Problem.** Sie kollidieren nicht über Namen, sie
275
267
  *installieren sich doppelt*, wenn zwei u2-Kopien laufen. Alle 10 hängen ihre Listener an
276
268
  `document`, vier davon (`confirm`, `dropzone`, `draghandle`, `movable`) benutzen
@@ -278,4 +270,67 @@ Erst ab **Phase 3** relevant:
278
270
  Scope zu binden würde sie kaputtmachen.
279
271
  Zu klären ist deshalb nicht „wie scopen wir", sondern zweierlei: **einmal pro Dokument**
280
272
  installieren, und **pro Modul** entscheiden, welche Wirkung wirklich subtree-lokal ist.
273
+
274
+ ## Erledigt in 1.5.2 — `U2Element`
275
+
276
+ [`u2/Element.js`](Element.js): Basisklasse, die den Elementen die Registry-Arbeit abnimmt.
277
+ `attachShadow()` belegt `customElementRegistry: this.customElementRegistry` vor — alle
278
+ Aufrufstellen bleiben dadurch **wortgleich wie vorher**. `this.useEl(name)` importiert
279
+ `el/<name>/<name>.js` und definiert die Klasse in die eigene Registry (`?? customElements`).
280
+ Async ist unkritisch: ein Element upgraded auch, wenn es erst nach dem `innerHTML` definiert wird.
281
+
282
+ Dazu `useClass(name)` und `useAttr(name)`. Alle drei holen ausserdem die **CSS ins eigene Shadow** —
283
+ u2-Elemente haben meist kein eigenes Shadow (`ico` schreibt sein SVG ins Light DOM, `ico.css` stylt
284
+ `u2-ico { … }` von aussen), CSS gehört deshalb immer dem Root, in dem das Kind steht. Der Vater
285
+ musste sie bisher von Hand per `@import` holen, und `fields` vergass `responsive.css` dabei.
286
+
287
+ **Keine Deklaration am Kind.** Ein `static hasCss` müsste erst per JS geladen werden, bevor die CSS
288
+ überhaupt startet — es macht den wichtigeren Request garantiert langsamer. `projects.json` wäre
289
+ ebenfalls erst ein Fetch (und ist Enhance-Territorium). Also beides parallel, bedingungslos, und die
290
+ Defaults kommen aus der Kategorie — dieselben wie in `enhance`:
291
+
292
+ | | js | css | Dateien |
293
+ |---|---|---|---|
294
+ | `el` | ja | ja | js 41/41, css 37/41 |
295
+ | `class` | **nein** | ja | js 0/8, css 8/8 |
296
+ | `attr` | ja | **nein** | js 15/16, css 1/16 |
297
+
298
+ Die Ausreisser gehören an die *Aufrufstelle*, nicht als Feld an alle 41 Elemente:
299
+ `useAttr('skin', {css:true})`. `skin` bleibt dauerhaft ein Attribut, weil es einen Wert trägt —
300
+ als Klasse ginge das erst mit einem Selektor wie `.u2-class-*`.
301
+
302
+ **Reihenfolge:** adoptierte Sheets kommen *nach* den Tree-Styles, die Kind-CSS würde also die
303
+ Overrides des Vaters schlagen. `use()` macht darum `unshift`, nicht `push` — sobald der `<style>`
304
+ eines Vaters auch mal adoptiert wird, steht er hinter den Kindern, unabhängig davon, wann deren
305
+ Sheet eintrifft. Solange der Vater ein Tree-`<style>` hat, muss er die Kind-Regeln
306
+ **überspezifizieren**. Ein echter Fall bestand: `fields` überschrieb `u2-responsive {display:block}`
307
+ mit `u2-responsive {display:grid}` — Gleichstand, jetzt `#container`.
308
+
309
+ `U2Element` ist keine Migration, sondern eine Regel: **wer ein u2-Element in sein eigenes Shadow
310
+ rendert, erbt davon.** Das sind heute genau diese sechs — die übrigen 35 öffnen entweder kein
311
+ Shadow oder rendern nichts von u2 hinein und brauchen die Basisklasse nicht.
312
+
313
+ | Modul | Kind |
314
+ |---|---|
315
+ | `alert`, `accordion`, `calendar`, `rating` | `ico` |
316
+ | `input` | `ico`, `bytes` |
317
+ | `fields` | `responsive` |
318
+
319
+ ```js
320
+ export default class U2Alert extends U2Element {
321
+ constructor() {
322
+ super();
323
+ this.attachShadow({mode: 'open'}); // erbt die Registry
324
+ this.useEl('ico'); // Klasse in die Registry + ico.css ins Shadow
325
+ this.useAttr('focusgroup'); // nur js
326
+ ```
327
+
328
+ Ausserhalb eines scoped Roots ist `this.customElementRegistry` `undefined`, die Option gilt als
329
+ abwesend und `useEl()` fällt auf `customElements` zurück — Verhalten unverändert.
330
+
331
+ Nicht betroffen, bewusst: **Attribute** (`focusgroup`, `draghandle`, `dropzone`, …) sind kein
332
+ Registry-Thema; `table` erzeugt sein `u2-ico` im **Light DOM**, wo `enhance` es ohnehin sieht;
333
+ `input`s `u2-badge` ist eine *Klasse*, kein Tag; `tree`s `u2-ico` ist auskommentiert.
334
+ `U2CalendarItem` erbt `U2Element` mit, obwohl sein Shadow heute keine Custom Elements enthält.
335
+
281
336
  - Scoped-Verkettung von Kind-Elementen in die Eltern-Registry (siehe „Interne Deps").
package/u2/utils.js CHANGED
@@ -59,3 +59,4 @@ function rootGetStyleWrapper(root) {
59
59
  }
60
60
  return styleWrapper;
61
61
  }
62
+