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.
- package/el/accordion/accordion.js +1 -1
- package/el/alert/alert.css +1 -1
- package/el/alert/alert.js +1 -1
- package/el/breadcrumb/breadcrumb.js +1 -1
- package/el/buttongroup/buttongroup.js +1 -1
- package/el/bytes/bytes.js +1 -1
- package/el/calendar/calendar.css +2 -2
- package/el/calendar/calendar.js +2 -2
- package/el/carousel/carousel.js +1 -1
- package/el/carousel-nav/carousel-nav.js +1 -1
- package/el/chart/chart.js +1 -1
- package/el/code/code.js +1 -1
- package/el/cookiebanner/cookiebanner.js +1 -1
- package/el/copy/copy.js +1 -1
- package/el/counter/counter.js +1 -1
- package/el/drawer/drawer.js +1 -1
- package/el/fields/fields.js +1 -1
- package/el/ico/ico.js +1 -1
- package/el/input/input.css +2 -2
- package/el/input/input.js +1 -1
- package/el/maintitlebar/maintitlebar.css +1 -1
- package/el/maintitlebar/maintitlebar.js +1 -1
- package/el/masonry/masonry.js +1 -1
- package/el/menubutton/menubutton.js +1 -1
- package/el/number/number.js +1 -1
- package/el/out/out.js +1 -1
- package/el/pagination/pagination.css +3 -3
- package/el/pagination/pagination.js +1 -1
- package/el/parallax-bg/parallax-bg.js +1 -1
- package/el/qrcode/qrcode.js +1 -1
- package/el/rating/rating.js +1 -1
- package/el/responsive/responsive.js +1 -1
- package/el/rte/rte.css +1 -1
- package/el/rte/rte.js +1 -1
- package/el/skeleton/skeleton.js +1 -1
- package/el/splitpanel/splitpanel.js +1 -1
- package/el/spot/spot.js +1 -1
- package/el/system/styler.js +1 -1
- package/el/system/system.js +1 -1
- package/el/table/table.js +1 -1
- package/el/tabs/tabs.js +1 -1
- package/el/textfit/textfit.js +1 -1
- package/el/time/time.js +1 -1
- package/el/toc/toc.js +1 -1
- package/el/tooltip/tooltip.js +1 -1
- package/el/tree/tree.js +1 -1
- package/el/typewriter/typewriter.js +1 -1
- package/el/video/video.js +1 -1
- package/package.json +1 -1
- package/u2/auto.js +3 -50
- package/u2/enhance.js +145 -0
- package/u2/plan-scoped-refactoring.md +65 -20
- package/u2/tools/update.skill.js +4 -0
- package/u2/u2.js +10 -10
- package/u2/utils.js +1 -1
package/el/alert/alert.css
CHANGED
|
@@ -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');
|
package/el/bytes/bytes.js
CHANGED
package/el/calendar/calendar.css
CHANGED
|
@@ -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,
|
|
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,
|
|
14
|
+
outline: 2px dotted var(--color-text, CanvasText);
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
17
|
|
package/el/calendar/calendar.js
CHANGED
|
@@ -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);
|
package/el/carousel/carousel.js
CHANGED
package/el/chart/chart.js
CHANGED
package/el/code/code.js
CHANGED
package/el/copy/copy.js
CHANGED
package/el/counter/counter.js
CHANGED
package/el/drawer/drawer.js
CHANGED
package/el/fields/fields.js
CHANGED
package/el/ico/ico.js
CHANGED
package/el/input/input.css
CHANGED
|
@@ -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,
|
|
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,
|
|
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 => {
|
|
@@ -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);
|
package/el/masonry/masonry.js
CHANGED
|
@@ -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) {
|
package/el/number/number.js
CHANGED
|
@@ -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
|
@@ -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/qrcode/qrcode.js
CHANGED
package/el/rating/rating.js
CHANGED
package/el/rte/rte.css
CHANGED
package/el/rte/rte.js
CHANGED
package/el/skeleton/skeleton.js
CHANGED
package/el/spot/spot.js
CHANGED
package/el/system/styler.js
CHANGED
package/el/system/system.js
CHANGED
|
@@ -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
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
|
package/el/textfit/textfit.js
CHANGED
package/el/time/time.js
CHANGED
package/el/toc/toc.js
CHANGED
package/el/tooltip/tooltip.js
CHANGED
|
@@ -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
|
@@ -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.
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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 (!('
|
|
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
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
-
**
|
|
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
|
-
|
|
159
|
-
|
|
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
|
|
167
|
-
`fields`→`responsive`.
|
|
196
|
+
Betroffen: `accordion`→`ico`, `alert`→`ico`, `calendar`→`ico`, `rating`→`ico`,
|
|
197
|
+
`input`→`ico`+`bytes`, `fields`→`responsive`.
|
|
168
198
|
|
|
169
|
-
>
|
|
170
|
-
>
|
|
171
|
-
>
|
|
172
|
-
>
|
|
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
|
-
-
|
|
235
|
-
`
|
|
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").
|
package/u2/tools/update.skill.js
CHANGED
|
@@ -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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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);
|