u2ui 1.4.6 → 1.5.1

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.
Files changed (55) hide show
  1. package/el/accordion/accordion.js +1 -1
  2. package/el/alert/alert.css +1 -1
  3. package/el/alert/alert.js +1 -1
  4. package/el/breadcrumb/breadcrumb.js +1 -1
  5. package/el/buttongroup/buttongroup.js +1 -1
  6. package/el/bytes/bytes.js +1 -1
  7. package/el/calendar/calendar.css +2 -2
  8. package/el/calendar/calendar.js +2 -2
  9. package/el/carousel/carousel.js +1 -1
  10. package/el/carousel-nav/carousel-nav.js +1 -1
  11. package/el/chart/chart.js +1 -1
  12. package/el/code/code.js +1 -1
  13. package/el/cookiebanner/cookiebanner.js +1 -1
  14. package/el/copy/copy.js +1 -1
  15. package/el/counter/counter.js +1 -1
  16. package/el/drawer/drawer.js +1 -1
  17. package/el/fields/fields.js +1 -1
  18. package/el/ico/ico.js +1 -1
  19. package/el/input/input.css +2 -2
  20. package/el/input/input.js +1 -1
  21. package/el/maintitlebar/maintitlebar.css +1 -1
  22. package/el/maintitlebar/maintitlebar.js +1 -1
  23. package/el/masonry/masonry.js +1 -1
  24. package/el/menubutton/menubutton.js +1 -1
  25. package/el/number/number.js +1 -1
  26. package/el/out/out.js +1 -1
  27. package/el/pagination/pagination.css +3 -3
  28. package/el/pagination/pagination.js +1 -1
  29. package/el/parallax-bg/parallax-bg.js +1 -1
  30. package/el/qrcode/qrcode.js +1 -1
  31. package/el/rating/rating.js +1 -1
  32. package/el/responsive/responsive.js +1 -1
  33. package/el/rte/rte.css +1 -1
  34. package/el/rte/rte.js +1 -1
  35. package/el/skeleton/skeleton.js +1 -1
  36. package/el/splitpanel/splitpanel.js +1 -1
  37. package/el/spot/spot.js +1 -1
  38. package/el/system/styler.js +1 -1
  39. package/el/system/system.js +1 -1
  40. package/el/table/table.js +1 -1
  41. package/el/tabs/tabs.js +1 -1
  42. package/el/textfit/textfit.js +1 -1
  43. package/el/time/time.js +1 -1
  44. package/el/toc/toc.js +1 -1
  45. package/el/tooltip/tooltip.js +1 -1
  46. package/el/tree/tree.js +1 -1
  47. package/el/typewriter/typewriter.js +1 -1
  48. package/el/video/video.js +1 -1
  49. package/package.json +1 -1
  50. package/u2/auto.js +3 -50
  51. package/u2/enhance.js +145 -0
  52. package/u2/plan-scoped-refactoring.md +65 -20
  53. package/u2/tools/update.skill.js +4 -0
  54. package/u2/u2.js +10 -10
  55. package/u2/utils.js +1 -1
@@ -208,4 +208,4 @@ export default class U2Accordion extends HTMLElement {
208
208
  }
209
209
  }
210
210
 
211
- customElements.define('u2-accordion', U2Accordion);
211
+ customElements.get('u2-accordion') || customElements.define('u2-accordion', U2Accordion);
@@ -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;
package/el/alert/alert.js CHANGED
@@ -163,7 +163,7 @@ const variantData = {
163
163
  }
164
164
  };
165
165
 
166
- customElements.define('u2-alert', U2Alert);
166
+ customElements.get('u2-alert') || customElements.define('u2-alert', U2Alert);
167
167
 
168
168
  function alert(message, variant, options={}) {
169
169
  const el = document.createElement('u2-alert');
@@ -116,4 +116,4 @@ export default class U2Breadcrumb extends HTMLElement {
116
116
  }
117
117
  }
118
118
 
119
- customElements.define('u2-breadcrumb', U2Breadcrumb);
119
+ customElements.get('u2-breadcrumb') || customElements.define('u2-breadcrumb', U2Breadcrumb);
@@ -65,4 +65,4 @@ export default class U2Buttongroup extends HTMLElement {
65
65
  }
66
66
 
67
67
  }
68
- customElements.define('u2-buttongroup', U2Buttongroup);
68
+ customElements.get('u2-buttongroup') || customElements.define('u2-buttongroup', U2Buttongroup);
package/el/bytes/bytes.js CHANGED
@@ -54,7 +54,7 @@ export default class U2Bytes extends HTMLElement {
54
54
  }
55
55
  }
56
56
 
57
- customElements.define('u2-bytes', U2Bytes);
57
+ customElements.get('u2-bytes') || customElements.define('u2-bytes', U2Bytes);
58
58
 
59
59
 
60
60
  function localesFromElement(el) {
@@ -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
 
@@ -387,5 +387,5 @@ function getTimeFormatter(localeTag, options = { hour: '2-digit', minute: '2-dig
387
387
  }
388
388
 
389
389
 
390
- customElements.define('u2-calendar', U2Calendar);
391
- customElements.define('u2-calendaritem', U2CalendarItem);
390
+ customElements.get('u2-calendar') || customElements.define('u2-calendar', U2Calendar);
391
+ customElements.get('u2-calendaritem') || customElements.define('u2-calendaritem', U2CalendarItem);
@@ -321,7 +321,7 @@ U2Carousel.mode.fade = {
321
321
  }
322
322
 
323
323
 
324
- customElements.define('u2-carousel', U2Carousel)
324
+ customElements.get('u2-carousel') || customElements.define('u2-carousel', U2Carousel)
325
325
 
326
326
 
327
327
  // slide on target
@@ -71,4 +71,4 @@ export default class U2CarouselNav extends HTMLElement {
71
71
  })
72
72
  }
73
73
  }
74
- customElements.define('u2-carousel-nav', U2CarouselNav)
74
+ customElements.get('u2-carousel-nav') || customElements.define('u2-carousel-nav', U2CarouselNav)
package/el/chart/chart.js CHANGED
@@ -185,4 +185,4 @@ function getHSLofElement(el) {
185
185
  }
186
186
 
187
187
 
188
- customElements.define('u2-chart', U2Chart);
188
+ customElements.get('u2-chart') || customElements.define('u2-chart', U2Chart);
package/el/code/code.js CHANGED
@@ -197,7 +197,7 @@ export default class U2Code extends HTMLElement {
197
197
 
198
198
  }
199
199
 
200
- customElements.define('u2-code', U2Code);
200
+ customElements.get('u2-code') || customElements.define('u2-code', U2Code);
201
201
 
202
202
 
203
203
  function trimCode(value){
@@ -55,7 +55,7 @@ function getCookie() {
55
55
  }
56
56
  }
57
57
 
58
- customElements.define('u2-cookiebanner', U2Cookiebanner);
58
+ customElements.get('u2-cookiebanner') || customElements.define('u2-cookiebanner', U2Cookiebanner);
59
59
 
60
60
 
61
61
 
package/el/copy/copy.js CHANGED
@@ -87,4 +87,4 @@ export default class U2Copy extends HTMLElement {
87
87
  }
88
88
  }
89
89
 
90
- customElements.define('u2-copy', U2Copy);
90
+ customElements.get('u2-copy') || customElements.define('u2-copy', U2Copy);
@@ -93,4 +93,4 @@ function format(el, val){
93
93
  }).format(val);
94
94
  }
95
95
 
96
- customElements.define('u2-counter', U2Counter)
96
+ customElements.get('u2-counter') || customElements.define('u2-counter', U2Counter)
@@ -163,4 +163,4 @@ export default class U2Drawer extends HTMLElement {
163
163
  }
164
164
 
165
165
  // Register custom element
166
- customElements.define('u2-drawer', U2Drawer);
166
+ customElements.get('u2-drawer') || customElements.define('u2-drawer', U2Drawer);
@@ -130,4 +130,4 @@ export default class U2Fields extends HTMLElement {
130
130
 
131
131
  }
132
132
 
133
- customElements.define('u2-fields', U2Fields);
133
+ customElements.get('u2-fields') || customElements.define('u2-fields', U2Fields);
package/el/ico/ico.js CHANGED
@@ -160,4 +160,4 @@ async function loadCached(url) {
160
160
  }
161
161
 
162
162
 
163
- customElements.define('u2-ico', U2Ico);
163
+ customElements.get('u2-ico') || customElements.define('u2-ico', U2Ico);
@@ -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
  }
package/el/input/input.js CHANGED
@@ -665,7 +665,7 @@ export default class U2Input extends HTMLElement {
665
665
  $(selector) { return this.shadowRoot.querySelector(selector); }
666
666
 
667
667
  }
668
- customElements.define('u2-input', U2Input);
668
+ customElements.get('u2-input') || customElements.define('u2-input', U2Input);
669
669
 
670
670
  // Globales File-Drag-Feedback
671
671
  document.addEventListener('dragenter', e => {
@@ -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
 
@@ -14,4 +14,4 @@ export default class U2Maintitlebar extends HTMLElement {
14
14
  navigator.windowControlsOverlay?.removeEventListener('geometrychange', this._check);
15
15
  }
16
16
  }
17
- customElements.define('u2-maintitlebar', U2Maintitlebar);
17
+ customElements.get('u2-maintitlebar') || customElements.define('u2-maintitlebar', U2Maintitlebar);
@@ -44,7 +44,7 @@ export default class U2Masonry extends HTMLElement{
44
44
  }
45
45
  }
46
46
 
47
- customElements.define('u2-masonry', U2Masonry);
47
+ customElements.get('u2-masonry') || customElements.define('u2-masonry', U2Masonry);
48
48
 
49
49
 
50
50
  function sortByHeight(a, b){
@@ -73,7 +73,7 @@ export default class U2Menubutton extends HTMLElement {
73
73
  });
74
74
  }
75
75
  }
76
- customElements.define('u2-menubutton', U2Menubutton);
76
+ customElements.get('u2-menubutton') || customElements.define('u2-menubutton', U2Menubutton);
77
77
 
78
78
 
79
79
  function nextFocusable(root) {
@@ -58,7 +58,7 @@ export default class U2Number extends HTMLElement {
58
58
  }
59
59
  }
60
60
 
61
- customElements.define('u2-number', U2Number);
61
+ customElements.get('u2-number') || customElements.define('u2-number', U2Number);
62
62
 
63
63
  function localesFromElement(el) {
64
64
  let langEl = el.closest('[lang]')||document.documentElement;
package/el/out/out.js CHANGED
@@ -39,4 +39,4 @@ export default class U2Out extends HTMLElement {
39
39
 
40
40
  }
41
41
 
42
- customElements.define('u2-out', U2Out);
42
+ customElements.get('u2-out') || customElements.define('u2-out', U2Out);
@@ -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
  }
@@ -144,4 +144,4 @@ export default class U2Pagination extends HTMLElement {
144
144
  }
145
145
  }
146
146
 
147
- customElements.define('u2-pagination', U2Pagination);
147
+ customElements.get('u2-pagination') || customElements.define('u2-pagination', U2Pagination);
@@ -194,4 +194,4 @@ export default class U2ParallaxBg extends HTMLElement {
194
194
  }
195
195
  }
196
196
 
197
- customElements.define('u2-parallax-bg', U2ParallaxBg)
197
+ customElements.get('u2-parallax-bg') || customElements.define('u2-parallax-bg', U2ParallaxBg)
@@ -35,7 +35,7 @@ export default class U2Qrcode extends HTMLElement {
35
35
  }
36
36
 
37
37
  }
38
- customElements.define('u2-qrcode', U2Qrcode);
38
+ customElements.get('u2-qrcode') || customElements.define('u2-qrcode', U2Qrcode);
39
39
 
40
40
  function toSvgString(qr) {
41
41
  const border = 0;
@@ -145,4 +145,4 @@ function translate(text) {
145
145
  return texts[text][lang] ?? texts[text].en;
146
146
  }
147
147
 
148
- customElements.define('u2-rating', U2Rating);
148
+ customElements.get('u2-rating') || customElements.define('u2-rating', U2Rating);
@@ -123,7 +123,7 @@ export default class U2Responsive extends HTMLElement {
123
123
  }
124
124
  }
125
125
 
126
- customElements.define('u2-responsive', U2Responsive);
126
+ customElements.get('u2-responsive') || customElements.define('u2-responsive', U2Responsive);
127
127
 
128
128
 
129
129
 
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/el/rte/rte.js CHANGED
@@ -44,7 +44,7 @@ export default class U2Rte extends HTMLElement {
44
44
  }
45
45
  }
46
46
 
47
- customElements.define('u2-rte', U2Rte);
47
+ customElements.get('u2-rte') || customElements.define('u2-rte', U2Rte);
48
48
 
49
49
 
50
50
  // import Showdown from 'https://cdn.skypack.dev/showdown';
@@ -21,7 +21,7 @@ export default class U2Skeleton extends HTMLElement {
21
21
  }
22
22
  }
23
23
 
24
- customElements.define('u2-skeleton', U2Skeleton);
24
+ customElements.get('u2-skeleton') || customElements.define('u2-skeleton', U2Skeleton);
25
25
 
26
26
 
27
27
 
@@ -114,4 +114,4 @@ export default class U2Splitpanel extends HTMLElement {
114
114
  }
115
115
  }
116
116
 
117
- customElements.define('u2-splitpanel', U2Splitpanel);
117
+ customElements.get('u2-splitpanel') || customElements.define('u2-splitpanel', U2Splitpanel);
package/el/spot/spot.js CHANGED
@@ -103,4 +103,4 @@ export default class U2Spot extends HTMLElement {
103
103
  }
104
104
  }
105
105
 
106
- customElements.define('u2-spot', U2Spot);
106
+ customElements.get('u2-spot') || customElements.define('u2-spot', U2Spot);
@@ -572,7 +572,7 @@ class ColorSystemStyler extends HTMLElement {
572
572
  }
573
573
  }
574
574
 
575
- customElements.define('u2-system-styler', ColorSystemStyler);
575
+ customElements.get('u2-system-styler') || customElements.define('u2-system-styler', ColorSystemStyler);
576
576
 
577
577
 
578
578
 
@@ -68,7 +68,7 @@ export default class U2System extends HTMLElement {
68
68
 
69
69
  }
70
70
 
71
- customElements.define('u2-system', U2System);
71
+ customElements.get('u2-system') || customElements.define('u2-system', U2System);
72
72
 
73
73
  addEventListener('keydown', (e) => {
74
74
  if (e.target.form !== undefined) return;
package/el/table/table.js CHANGED
@@ -465,4 +465,4 @@ class Column {
465
465
  }
466
466
  }
467
467
 
468
- customElements.define('u2-table', U2Table);
468
+ customElements.get('u2-table') || customElements.define('u2-table', U2Table);
package/el/tabs/tabs.js CHANGED
@@ -162,7 +162,7 @@ export default class U2Tabs extends HTMLElement {
162
162
  }
163
163
 
164
164
  }
165
- customElements.define('u2-tabs', U2Tabs);
165
+ customElements.get('u2-tabs') || customElements.define('u2-tabs', U2Tabs);
166
166
 
167
167
  // beta, does not work if initial u2-target is already fired
168
168
  // problem: sometimes, on initial load, the event is fired before the listener is added
@@ -77,4 +77,4 @@ export default class U2Textfit extends HTMLElement {
77
77
  }
78
78
  }
79
79
 
80
- customElements.define('u2-textfit', U2Textfit);
80
+ customElements.get('u2-textfit') || customElements.define('u2-textfit', U2Textfit);
package/el/time/time.js CHANGED
@@ -176,4 +176,4 @@ const ensureOption = (val, ...options) => {
176
176
  return options[0];
177
177
  }
178
178
 
179
- customElements.define('u2-time', U2Time);
179
+ customElements.get('u2-time') || customElements.define('u2-time', U2Time);
package/el/toc/toc.js CHANGED
@@ -84,4 +84,4 @@ export default class U2Toc extends HTMLElement {
84
84
  }
85
85
  }
86
86
 
87
- customElements.define('u2-toc', U2Toc);
87
+ customElements.get('u2-toc') || customElements.define('u2-toc', U2Toc);
@@ -67,7 +67,7 @@ export default class U2Tooltip extends HTMLElement {
67
67
  }
68
68
  static observedAttributes = ['position'];
69
69
  }
70
- customElements.define('u2-tooltip', U2Tooltip);
70
+ customElements.get('u2-tooltip') || customElements.define('u2-tooltip', U2Tooltip);
71
71
 
72
72
 
73
73
  document.addEventListener('mouseenter',function(e){
package/el/tree/tree.js CHANGED
@@ -233,4 +233,4 @@ export default class U2Tree extends HTMLElement {
233
233
  }
234
234
  }
235
235
 
236
- customElements.define('u2-tree', U2Tree);
236
+ customElements.get('u2-tree') || customElements.define('u2-tree', U2Tree);
@@ -100,7 +100,7 @@ export default class U2Typewriter extends HTMLElement {
100
100
  }
101
101
 
102
102
  }
103
- customElements.define('u2-typewriter', U2Typewriter);
103
+ customElements.get('u2-typewriter') || customElements.define('u2-typewriter', U2Typewriter);
104
104
 
105
105
 
106
106
  // find the next Char in children or sibling of the node but not outside the root
package/el/video/video.js CHANGED
@@ -483,7 +483,7 @@ const svg = {
483
483
  fastRewind: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M11 16.07V7.93c0-.81-.91-1.28-1.58-.82l-5.77 4.07c-.56.4-.56 1.24 0 1.63l5.77 4.07c.67.47 1.58 0 1.58-.81zm1.66-3.25l5.77 4.07c.66.47 1.58-.01 1.58-.82V7.93c0-.81-.91-1.28-1.58-.82l-5.77 4.07a1 1 0 0 0 0 1.64z"></path></svg>',
484
484
  }
485
485
 
486
- customElements.define('u2-video', U2Video);
486
+ customElements.get('u2-video') || customElements.define('u2-video', U2Video);
487
487
 
488
488
 
489
489
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "u2ui",
3
- "version": "1.4.6",
3
+ "version": "1.5.1",
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);