u2ui 1.4.5 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/attr/confirm/confirm.js +2 -1
- package/class/unstyle/unstyle.css +1 -0
- package/el/alert/alert.css +1 -1
- package/el/calendar/calendar.css +2 -2
- package/el/input/input.css +2 -2
- package/el/maintitlebar/maintitlebar.css +1 -1
- package/el/pagination/pagination.css +3 -3
- package/el/rte/rte.css +1 -1
- package/el/toc/toc.js +2 -0
- package/package.json +1 -1
- package/u2/auto.js +6 -58
- 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/attr/confirm/confirm.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
let resentConfirmed = false;
|
|
3
3
|
|
|
4
4
|
// submit
|
|
5
|
-
//
|
|
5
|
+
// idea: test if it works inside shadow dom
|
|
6
|
+
// idea: Alt+click to skip confirmation? but how in the submit path?
|
|
6
7
|
|
|
7
8
|
document.addEventListener('submit', e=>{
|
|
8
9
|
if (resentConfirmed) return; // dont check again
|
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/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/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
|
}
|
|
@@ -9,19 +9,19 @@ u2-pagination nav {
|
|
|
9
9
|
|
|
10
10
|
& a {
|
|
11
11
|
display: block;
|
|
12
|
-
padding: calc(var(--gap) / 2) var(--gap);
|
|
12
|
+
padding: calc(var(--gap, 1rem) / 2) var(--gap, 1rem);
|
|
13
13
|
text-decoration: none;
|
|
14
14
|
color: inherit;
|
|
15
15
|
text-align: center;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
& a:hover {
|
|
19
|
-
background: var(--color-light);
|
|
19
|
+
background: var(--color-light, color-mix(in srgb, CanvasText, Canvas 88%));
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
& a[aria-current="page"] {
|
|
23
23
|
font-weight: bold;
|
|
24
|
-
background: var(--color);
|
|
24
|
+
background: var(--color, CanvasText);
|
|
25
25
|
color: white;
|
|
26
26
|
}
|
|
27
27
|
}
|
package/el/rte/rte.css
CHANGED
package/el/toc/toc.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "u2ui",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "HTML Advanced: More Capabilities, Same Feel, Same Simplicity",
|
|
5
5
|
"keywords": ["web-components","pattern-library","component-library","ui-toolkit","design-system"],
|
|
6
6
|
"author": "Tobias Buschor",
|
package/u2/auto.js
CHANGED
|
@@ -7,33 +7,21 @@ let rootUrl = root.toString();
|
|
|
7
7
|
|
|
8
8
|
if (root.host === 'localhost') debug = true;
|
|
9
9
|
|
|
10
|
-
//let min = '.min';
|
|
11
|
-
|
|
12
|
-
// get faster cdn updates? https://purge.jsdelivr.net/gh/nuxodin/cleanup.js@latest/
|
|
13
|
-
|
|
14
10
|
if (debug) { // top level await safari >= 15.1
|
|
15
|
-
//min = '';
|
|
16
11
|
await Promise.all([
|
|
17
|
-
//import('https://cdn.jsdelivr.net/gh/nuxodin/lazyfill/mod.
|
|
18
|
-
//import('https://cdn.jsdelivr.net/gh/nuxodin/lazyfill/htmlfills.
|
|
19
|
-
//import('https://cdn.jsdelivr.net/gh/nuxodin/cleanup.js/mod.
|
|
12
|
+
//import('https://cdn.jsdelivr.net/gh/nuxodin/lazyfill/mod.js'),
|
|
13
|
+
//import('https://cdn.jsdelivr.net/gh/nuxodin/lazyfill/htmlfills.js'),
|
|
14
|
+
//import('https://cdn.jsdelivr.net/gh/nuxodin/cleanup.js/mod.js'),
|
|
20
15
|
]);
|
|
21
16
|
}
|
|
22
17
|
|
|
23
18
|
import {importCss} from './utils.js';
|
|
24
|
-
import {
|
|
25
|
-
|
|
26
|
-
const projects = await repos();
|
|
19
|
+
import {enhance} from './enhance.js';
|
|
27
20
|
|
|
28
21
|
let prio = 1;
|
|
29
22
|
setTimeout(()=>prio = 2);
|
|
30
23
|
setTimeout(()=>prio = 3, 2000);
|
|
31
24
|
const needed = { js:{}, css:{} };
|
|
32
|
-
function impJs(url){
|
|
33
|
-
if (!url) return;
|
|
34
|
-
needed.js[url] ??= prio;
|
|
35
|
-
return import(url);
|
|
36
|
-
}
|
|
37
25
|
function impCss(url, options={}){
|
|
38
26
|
if (!url || url in needed.css) return;
|
|
39
27
|
importCss(url, options).then(res=>{
|
|
@@ -69,49 +57,9 @@ impCss(rootUrl+'css/classless/more.css');
|
|
|
69
57
|
////////////////////////////////////////////////////////////////
|
|
70
58
|
|
|
71
59
|
|
|
72
|
-
|
|
73
|
-
if (node.tagName.startsWith('U2-')) {
|
|
74
|
-
let name = node.tagName.substring(3).toLowerCase();
|
|
75
|
-
if (!customElements.get('u2-'+name)) {
|
|
76
|
-
loadProject('el', name);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
for (const klass of node.classList) {
|
|
80
|
-
if (!klass.startsWith('u2-')) continue;
|
|
81
|
-
let name = klass.substring(3);
|
|
82
|
-
loadProject('class', name);
|
|
83
|
-
}
|
|
84
|
-
for (const attr of node.attributes) {
|
|
85
|
-
if (!attr.name.startsWith('u2-')) continue;
|
|
86
|
-
let name = attr.name.substring(3).replace(/-.*/,''); // example: u2-href-target => href
|
|
87
|
-
loadProject('attr', name);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function loadProject(category, name){
|
|
92
|
-
const id = category + '/' + name;
|
|
93
|
-
const meta = projects[id];
|
|
94
|
-
if (!meta) { console.warn('u2: project not found:', category+'/'+name); return; }
|
|
95
|
-
const hasJs = meta.js ?? (category==='el' || category==='attr');
|
|
96
|
-
const hasCss = meta.css ?? (category==='el' || category==='class');
|
|
97
|
-
hasCss && impCss(rootUrl + id + '/' + name + '.css');
|
|
98
|
-
hasJs && impJs(rootUrl + id + '/' + name + '.js');
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function newNodeRoot(node){
|
|
102
|
-
if (!node.tagName) return;
|
|
103
|
-
newNode(node);
|
|
104
|
-
node.querySelectorAll('*').forEach(newNode);
|
|
105
|
-
}
|
|
106
|
-
var mo = new MutationObserver((entries)=>{
|
|
107
|
-
for (const entry of entries) {
|
|
108
|
-
for (const node of entry.addedNodes) newNodeRoot(node);
|
|
109
|
-
}
|
|
110
|
-
});
|
|
111
|
-
|
|
60
|
+
/** enhance() does the scanning and loading; this file only records it for the ui. */
|
|
112
61
|
export function addShadowRoot(rootNode){
|
|
113
|
-
|
|
114
|
-
newNodeRoot(rootNode);
|
|
62
|
+
return enhance(rootNode, { onLoad: (kind, url) => needed[kind][url] ??= prio });
|
|
115
63
|
}
|
|
116
64
|
addShadowRoot(document.documentElement)
|
|
117
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);
|