u2ui 1.5.1 → 1.5.2

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.2",
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,37 @@
1
+ import { sheet } from './utils.js';
2
+
3
+ const base = new URL('../', import.meta.url).href; // this copy of u2
4
+
5
+ /** Base class for u2 elements — keeps a subtree on one u2 version.
6
+ A scoped shadow root only resolves tags its own registry knows, and a shadow opened
7
+ without the option silently falls back to the global one. Inheriting it instead means
8
+ nesting composes: whatever version enhanced the root stays the version all the way down. */
9
+ export default class U2Element extends HTMLElement {
10
+
11
+ attachShadow(options) {
12
+ return super.attachShadow({customElementRegistry: this.customElementRegistry, ...options});
13
+ }
14
+
15
+ useEl(name, options) { return use(this, 'el', name, options); }
16
+ useClass(name, options) { return use(this, 'class', name, options); }
17
+ useAttr(name, options) { return use(this, 'attr', name, options); }
18
+ }
19
+
20
+ /** Bring what a child needs into this element's shadow. Both start at once — the css is the
21
+ part that shows, it must not wait for a module. Defaults follow the category: an el has
22
+ both, a class is css only, an attr is js only — `u2-skin` is the attr that styles, and it
23
+ stays an attr because it carries a value: useAttr('skin', {css:true}). */
24
+ function use(el, category, name, {css = category !== 'attr', js = category !== 'class'} = {}) {
25
+ const path = `${base}${category}/${name}/${name}`;
26
+ const warn = err => console.warn('u2: could not load', category + '/' + name, err);
27
+ // unshift, not push: the element's own styles are adopted after these and have to keep
28
+ // winning ties, no matter when the child's sheet arrives.
29
+ if (css) sheet(path + '.css').then(s => el.shadowRoot.adoptedStyleSheets.unshift(s), warn);
30
+ if (!js) return;
31
+ return import(path + '.js').then(mod => {
32
+ if (category !== 'el') return; // a class has no js, an attr installs itself
33
+ const registry = el.customElementRegistry ?? customElements;
34
+ const tag = 'u2-' + name;
35
+ registry.get(tag) || registry.define(tag, mod.default);
36
+ }, warn);
37
+ }
package/u2/enhance.js CHANGED
@@ -9,29 +9,18 @@ document it stays a <link>, so the page keeps its say over u2's own rules.
9
9
  See ./plan-scoped-refactoring.md */
10
10
 
11
11
  import { repos } from './u2.js';
12
- import { importCss } from './utils.js';
12
+ import { importCss, sheet } from './utils.js';
13
13
 
14
14
  const base = new URL('../', import.meta.url).href; // this copy of u2 — another root may run another
15
15
  const scoped = 'customElementRegistry' in ShadowRoot.prototype;
16
16
  // The default scope: module identity gives one per u2 version, so a caller who does not care
17
17
  // gets isolation for free. A caller who brings its own wins, and the elements follow the root.
18
18
  const versionRegistry = scoped ? new CustomElementRegistry() : undefined;
19
- const sheets = new Map(); // url -> Promise<CSSStyleSheet>, shared across roots
20
19
 
21
20
  // the manifest is wanted by every load, so fetch it on import and keep the resolved map
22
21
  let projects = null;
23
22
  repos().then(map => projects = map);
24
23
 
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
24
 
36
25
  /** Attach a shadow root that runs its own u2. Same signature as the native method. */
37
26
  export function attachShadow(host, options = {}) {
@@ -263,14 +263,9 @@ 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.
266
+ - **Die übrigen 36 Elemente auf `U2Element`.** Solange eines `extends HTMLElement` bleibt, fällt
267
+ sein Shadow auf `window.customElements` zurück, sobald es doch mal ein u2-Element rendert.
268
+ Mechanisch, aber 36 Dateien.
274
269
  - **Attribute sind kein Registry-Problem.** Sie kollidieren nicht über Namen, sie
275
270
  *installieren sich doppelt*, wenn zwei u2-Kopien laufen. Alle 10 hängen ihre Listener an
276
271
  `document`, vier davon (`confirm`, `dropzone`, `draghandle`, `movable`) benutzen
@@ -278,4 +273,65 @@ Erst ab **Phase 3** relevant:
278
273
  Scope zu binden würde sie kaputtmachen.
279
274
  Zu klären ist deshalb nicht „wie scopen wir", sondern zweierlei: **einmal pro Dokument**
280
275
  installieren, und **pro Modul** entscheiden, welche Wirkung wirklich subtree-lokal ist.
276
+
277
+ ## Erledigt in 1.5.2 — `U2Element`
278
+
279
+ [`u2/Element.js`](Element.js): Basisklasse, die den Elementen die Registry-Arbeit abnimmt.
280
+ `attachShadow()` belegt `customElementRegistry: this.customElementRegistry` vor — alle
281
+ Aufrufstellen bleiben dadurch **wortgleich wie vorher**. `this.useEl(name)` importiert
282
+ `el/<name>/<name>.js` und definiert die Klasse in die eigene Registry (`?? customElements`).
283
+ Async ist unkritisch: ein Element upgraded auch, wenn es erst nach dem `innerHTML` definiert wird.
284
+
285
+ Dazu `useClass(name)` und `useAttr(name)`. Alle drei holen ausserdem die **CSS ins eigene Shadow** —
286
+ u2-Elemente haben meist kein eigenes Shadow (`ico` schreibt sein SVG ins Light DOM, `ico.css` stylt
287
+ `u2-ico { … }` von aussen), CSS gehört deshalb immer dem Root, in dem das Kind steht. Der Vater
288
+ musste sie bisher von Hand per `@import` holen, und `fields` vergass `responsive.css` dabei.
289
+
290
+ **Keine Deklaration am Kind.** Ein `static hasCss` müsste erst per JS geladen werden, bevor die CSS
291
+ überhaupt startet — es macht den wichtigeren Request garantiert langsamer. `projects.json` wäre
292
+ ebenfalls erst ein Fetch (und ist Enhance-Territorium). Also beides parallel, bedingungslos, und die
293
+ Defaults kommen aus der Kategorie — dieselben wie in `enhance`:
294
+
295
+ | | js | css | Dateien |
296
+ |---|---|---|---|
297
+ | `el` | ja | ja | js 41/41, css 37/41 |
298
+ | `class` | **nein** | ja | js 0/8, css 8/8 |
299
+ | `attr` | ja | **nein** | js 15/16, css 1/16 |
300
+
301
+ Die Ausreisser gehören an die *Aufrufstelle*, nicht als Feld an alle 41 Elemente:
302
+ `useAttr('skin', {css:true})`. `skin` bleibt dauerhaft ein Attribut, weil es einen Wert trägt —
303
+ als Klasse ginge das erst mit einem Selektor wie `.u2-class-*`.
304
+
305
+ **Reihenfolge:** adoptierte Sheets kommen *nach* den Tree-Styles, die Kind-CSS würde also die
306
+ Overrides des Vaters schlagen. `use()` macht darum `unshift`, nicht `push` — sobald der `<style>`
307
+ eines Vaters auch mal adoptiert wird, steht er hinter den Kindern, unabhängig davon, wann deren
308
+ Sheet eintrifft. Solange der Vater ein Tree-`<style>` hat, muss er die Kind-Regeln
309
+ **überspezifizieren**. Ein echter Fall bestand: `fields` überschrieb `u2-responsive {display:block}`
310
+ mit `u2-responsive {display:grid}` — Gleichstand, jetzt `#container`.
311
+
312
+ Die sechs Module, die u2-Elemente in ihr *eigenes* Shadow rendern:
313
+
314
+ | Modul | Kind |
315
+ |---|---|
316
+ | `alert`, `accordion`, `calendar`, `rating` | `ico` |
317
+ | `input` | `ico`, `bytes` |
318
+ | `fields` | `responsive` |
319
+
320
+ ```js
321
+ export default class U2Alert extends U2Element {
322
+ constructor() {
323
+ super();
324
+ this.attachShadow({mode: 'open'}); // erbt die Registry
325
+ this.useEl('ico'); // Klasse in die Registry + ico.css ins Shadow
326
+ this.useAttr('focusgroup'); // nur js
327
+ ```
328
+
329
+ Ausserhalb eines scoped Roots ist `this.customElementRegistry` `undefined`, die Option gilt als
330
+ abwesend und `useEl()` fällt auf `customElements` zurück — Verhalten unverändert.
331
+
332
+ Nicht betroffen, bewusst: **Attribute** (`focusgroup`, `draghandle`, `dropzone`, …) sind kein
333
+ Registry-Thema; `table` erzeugt sein `u2-ico` im **Light DOM**, wo `enhance` es ohnehin sieht;
334
+ `input`s `u2-badge` ist eine *Klasse*, kein Tag; `tree`s `u2-ico` ist auskommentiert.
335
+ `U2CalendarItem` erbt `U2Element` mit, obwohl sein Shadow heute keine Custom Elements enthält.
336
+
281
337
  - Scoped-Verkettung von Kind-Elementen in die Eltern-Registry (siehe „Interne Deps").
package/u2/utils.js CHANGED
@@ -59,3 +59,20 @@ function rootGetStyleWrapper(root) {
59
59
  }
60
60
  return styleWrapper;
61
61
  }
62
+
63
+
64
+ ///////////////////////////
65
+ // stylesheets
66
+
67
+ const sheets = new Map();
68
+ /** One parsed CSSStyleSheet per url, shared by every root that adopts it. */
69
+ export function sheet(url) {
70
+ if (!sheets.has(url)) {
71
+ sheets.set(url, fetch(url).then(r => r.text()).then(css => {
72
+ const s = new CSSStyleSheet();
73
+ s.replaceSync(css); // u2 css has no relative url() and no @import
74
+ return s;
75
+ }));
76
+ }
77
+ return sheets.get(url);
78
+ }