@lark-apaas/coding-registry-buildless 0.1.20 → 0.1.22

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/coding-registry-buildless",
3
- "version": "0.1.20",
3
+ "version": "0.1.22",
4
4
  "description": "Miaoda registry (buildless runtime) — shadcn-style copy-in SDKs (globals + CDN-pinned, browser Babel) for buildless static HTML stacks",
5
5
  "type": "module",
6
6
  "files": [
@@ -0,0 +1,253 @@
1
+ /* BEGIN USAGE */
2
+ // android-frame.js — Simplified Android (Material 3) device frame, as custom elements.
3
+ // Based on Figma M3 spec. No dependencies, no image assets, no React.
4
+ // Defines: <android-device>, <android-status-bar>, <android-list-item>, <android-nav-bar>, <android-keyboard>
5
+ //
6
+ // Usage — wrap your screen content in <android-device> to get the bezel, status bar
7
+ // and gesture nav. Load with a plain <script src="android-frame.js"></script>:
8
+ //
9
+ // <android-device>
10
+ // ...your screen content...
11
+ // </android-device>
12
+ // <android-device dark keyboard>…</android-device>
13
+ // <android-device width="360" height="800">…</android-device> <!-- smaller device size -->
14
+ //
15
+ // Attributes:
16
+ // <android-device> width (412) · height (892) · dark · keyboard
17
+ // <android-status-bar> dark
18
+ // <android-list-item> headline · supporting · leading (one or two chars for the avatar)
19
+ // <android-nav-bar> dark
20
+ // Booleans are by attribute presence: `dark` on, absent off.
21
+ /* END USAGE */
22
+
23
+ (() => {
24
+ const MD_C = {
25
+ surface: '#f4fbf8',
26
+ surfaceVariant: '#dae5e1',
27
+ inverseOnSurface: '#ecf2ef',
28
+ secondaryContainer: '#cde8e1',
29
+ primaryFixedDim: '#83d5c6',
30
+ onSurface: '#171d1b',
31
+ onSurfaceVar: '#49454f',
32
+ onPrimaryContainer: '#00201c',
33
+ primary: '#006a60',
34
+ frameBorder: 'rgba(116,119,117,0.5)',
35
+ };
36
+ /**
37
+ * React version renders text and attributes through JSX, which escapes them. These
38
+ * elements build their shadow with innerHTML template strings, so that protection is
39
+ * gone — every value that comes from an author attribute, from persisted state, or
40
+ * from a Magic Share replay has to go through here. The artboard label matters most:
41
+ * it is renameable, it round-trips through the sidecar, AND a peer can push one in
42
+ * over Magic Share, so an unescaped one is script execution in the follower's page.
43
+ */
44
+ const esc = (v) =>
45
+ String(v ?? '').replace(
46
+ /[&<>"']/g,
47
+ (ch) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' })[ch],
48
+ );
49
+
50
+ // Non-numeric input falls through into a style="..." attribute, so it must be
51
+ // escaped too — a quote in it would break out of the attribute.
52
+ const px = (v) => (/^-?\d+(\.\d+)?$/.test(String(v)) ? `${v}px` : esc(v));
53
+
54
+ /**
55
+ * display:contents — the host generates no box of its own, so the shadow's root
56
+ * div becomes the parent's layout child directly. That is what keeps this port
57
+ * pixel-identical to the React version: the JSX component renders that same div
58
+ * with nothing wrapping it. `block` would add an outer box that swallows the
59
+ * flex properties meant for the inner one, showing up as a few pixels of drift
60
+ * in the visual regression.
61
+ */
62
+ /**
63
+ * The React version lives in the light DOM, so its box model comes from the
64
+ * product's own `*` reset (most products set border-box). That reset never
65
+ * reaches into a shadow root, and hardcoding either value gets half the products
66
+ * wrong. `inherit` is wrong too — it passes an inline border-box down to
67
+ * descendants, which a `*` rule would not. Copying the host's computed value is
68
+ * the only thing that matches the React version exactly: the host is a light DOM
69
+ * element, so the product's `*` rule does hit it.
70
+ */
71
+ const hostCss = (el) =>
72
+ '<style>:host{display:contents}*,*::before,*::after{box-sizing:' +
73
+ getComputedStyle(el).boxSizing +
74
+ '}</style>';
75
+
76
+ class Base extends HTMLElement {
77
+ constructor() {
78
+ super();
79
+ this.attachShadow({ mode: 'open' });
80
+ }
81
+ connectedCallback() {
82
+ this._render();
83
+ }
84
+ attributeChangedCallback() {
85
+ if (this.shadowRoot) this._render();
86
+ }
87
+ attr(name, fallback) {
88
+ const v = this.getAttribute(name);
89
+ return v === null || v === '' ? fallback : v;
90
+ }
91
+ get dark() {
92
+ return this.hasAttribute('dark');
93
+ }
94
+ }
95
+
96
+ // ───────────────────────────────────────────────────────────
97
+ // Status bar (time left, wifi/cell/battery right)
98
+ // ───────────────────────────────────────────────────────────
99
+ class AndroidStatusBar extends Base {
100
+ static get observedAttributes() {
101
+ return ['dark'];
102
+ }
103
+ _render() {
104
+ const c = this.dark ? '#fff' : MD_C.onSurface;
105
+ this.shadowRoot.innerHTML =
106
+ hostCss(this) +
107
+ `
108
+ <div part="frame" style="height:40px;display:flex;align-items:center;justify-content:space-between;
109
+ padding:0 16px;position:relative;font-family:Roboto,system-ui,sans-serif">
110
+ <div style="width:128px;display:flex;align-items:center;gap:8px">
111
+ <span style="font-size:14px;font-weight:400;letter-spacing:0.25px;line-height:20px;color:${c}">9:30</span>
112
+ </div>
113
+ <div style="position:absolute;left:50%;top:8px;transform:translateX(-50%);
114
+ width:24px;height:24px;border-radius:100px;background:#2e2e2e"></div>
115
+ <div style="display:flex;align-items:center">
116
+ <div style="display:flex;padding-right:2px">
117
+ <svg width="16" height="16" viewBox="0 0 16 16" style="margin-right:-2px">
118
+ <path d="M8 13.3L.67 5.97a10.37 10.37 0 0114.66 0L8 13.3z" fill="${c}"/>
119
+ </svg>
120
+ <svg width="16" height="16" viewBox="0 0 16 16" style="margin-right:-2px">
121
+ <path d="M14.67 14.67V1.33L1.33 14.67h13.34z" fill="${c}"/>
122
+ </svg>
123
+ </div>
124
+ <svg width="16" height="16" viewBox="0 0 16 16">
125
+ <rect x="3.75" y="2" width="8.5" height="13" rx="1.5" fill="${c}"/>
126
+ <rect x="5.5" y="0.9" width="5" height="2" rx="0.5" fill="${c}"/>
127
+ </svg>
128
+ </div>
129
+ </div>`;
130
+ }
131
+ }
132
+
133
+ // ───────────────────────────────────────────────────────────
134
+ // List item (Material 3)
135
+ // ───────────────────────────────────────────────────────────
136
+ class AndroidListItem extends Base {
137
+ static get observedAttributes() {
138
+ return ['headline', 'supporting', 'leading'];
139
+ }
140
+ _render() {
141
+ const leading = this.attr('leading', '');
142
+ const supporting = this.attr('supporting', '');
143
+ this.shadowRoot.innerHTML =
144
+ hostCss(this) +
145
+ `
146
+ <div part="frame" style="display:flex;align-items:center;gap:16px;padding:12px 16px;
147
+ min-height:56px;box-sizing:border-box;font-family:Roboto,system-ui,sans-serif">
148
+ ${
149
+ leading
150
+ ? `<div style="width:40px;height:40px;border-radius:50%;background:${MD_C.primary};color:#fff;
151
+ display:flex;align-items:center;justify-content:center;font-size:18px;font-weight:500;flex-shrink:0">${leading}</div>`
152
+ : ''
153
+ }
154
+ <div style="flex:1;min-width:0">
155
+ <div style="font-size:16px;color:${MD_C.onSurface};line-height:24px">${esc(this.attr('headline', ''))}</div>
156
+ ${supporting ? `<div style="font-size:14px;color:${MD_C.onSurfaceVar};line-height:20px">${esc(supporting)}</div>` : ''}
157
+ </div>
158
+ </div>`;
159
+ }
160
+ }
161
+
162
+ // ───────────────────────────────────────────────────────────
163
+ // Gesture nav bar (pill)
164
+ // ───────────────────────────────────────────────────────────
165
+ class AndroidNavBar extends Base {
166
+ static get observedAttributes() {
167
+ return ['dark'];
168
+ }
169
+ _render() {
170
+ this.shadowRoot.innerHTML =
171
+ hostCss(this) +
172
+ `
173
+ <div part="frame" style="height:24px;display:flex;align-items:center;justify-content:center">
174
+ <div style="width:108px;height:4px;border-radius:2px;
175
+ background:${this.dark ? '#fff' : MD_C.onSurface};opacity:0.4"></div>
176
+ </div>`;
177
+ }
178
+ }
179
+
180
+ // ───────────────────────────────────────────────────────────
181
+ // Keyboard — Gboard (Material 3)
182
+ // ───────────────────────────────────────────────────────────
183
+ class AndroidKeyboard extends Base {
184
+ _render() {
185
+ const key = (l, { flex = 1, bg = MD_C.surface, r = 6, minW, fs = 21 } = {}) =>
186
+ `<div style="height:46px;border-radius:${r}px;flex:${flex};${minW ? `min-width:${minW}px;` : ''}
187
+ background:${bg};display:flex;align-items:center;justify-content:center;
188
+ font-family:Roboto,system-ui;font-size:${fs}px;color:${MD_C.onPrimaryContainer}">${l}</div>`;
189
+ const row = (keys, style = '') =>
190
+ `<div style="display:flex;gap:6px;justify-content:center;${style}">${keys.map((l) => key(l)).join('')}</div>`;
191
+
192
+ this.shadowRoot.innerHTML =
193
+ hostCss(this) +
194
+ `
195
+ <div part="frame" style="background:${MD_C.inverseOnSurface};padding:0 8px 8px;
196
+ display:flex;flex-direction:column;gap:4px">
197
+ <div style="height:44px"></div>
198
+ <div style="display:flex;flex-direction:column;gap:12px">
199
+ ${row(['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p'])}
200
+ ${row(['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l'], 'padding:0 20px;')}
201
+ <div style="display:flex;gap:6px">
202
+ ${key('', { bg: MD_C.surfaceVariant })}
203
+ <div style="display:flex;gap:6px;flex:7;min-width:274px">
204
+ ${['z', 'x', 'c', 'v', 'b', 'n', 'm'].map((l) => key(l)).join('')}
205
+ </div>
206
+ ${key('', { bg: MD_C.surfaceVariant })}
207
+ </div>
208
+ <div style="display:flex;gap:6px">
209
+ ${key('?123', { bg: MD_C.secondaryContainer, r: 100, minW: 58, fs: 14 })}
210
+ ${key(',', { bg: MD_C.surfaceVariant })}
211
+ ${key('', { flex: 3, minW: 154 })}
212
+ ${key('.', { bg: MD_C.surfaceVariant })}
213
+ ${key('', { bg: MD_C.primaryFixedDim, r: 100, minW: 58 })}
214
+ </div>
215
+ </div>
216
+ </div>`;
217
+ }
218
+ }
219
+
220
+ // ───────────────────────────────────────────────────────────
221
+ // Device frame — wraps everything
222
+ // ───────────────────────────────────────────────────────────
223
+ class AndroidDevice extends Base {
224
+ static get observedAttributes() {
225
+ return ['width', 'height', 'dark', 'keyboard'];
226
+ }
227
+ _render() {
228
+ const dark = this.dark;
229
+ const d = dark ? ' dark' : '';
230
+ this.shadowRoot.innerHTML =
231
+ hostCss(this) +
232
+ `
233
+ <div part="frame" style="width:${px(this.attr('width', 412))};height:${px(this.attr('height', 892))};
234
+ border-radius:18px;overflow:hidden;background:${dark ? '#1d1b20' : MD_C.surface};
235
+ border:8px solid ${MD_C.frameBorder};display:flex;flex-direction:column;box-sizing:border-box">
236
+ <android-status-bar${d}></android-status-bar>
237
+ <div style="flex:1;overflow:auto"><slot></slot></div>
238
+ ${this.hasAttribute('keyboard') ? '<android-keyboard></android-keyboard>' : ''}
239
+ <android-nav-bar${d}></android-nav-bar>
240
+ </div>`;
241
+ }
242
+ }
243
+
244
+ for (const [tag, ctor] of [
245
+ ['android-status-bar', AndroidStatusBar],
246
+ ['android-list-item', AndroidListItem],
247
+ ['android-nav-bar', AndroidNavBar],
248
+ ['android-keyboard', AndroidKeyboard],
249
+ ['android-device', AndroidDevice],
250
+ ]) {
251
+ if (!customElements.get(tag)) customElements.define(tag, ctor);
252
+ }
253
+ })();
@@ -0,0 +1,246 @@
1
+ /* BEGIN USAGE */
2
+ // browser-window.js — Simplified Chrome browser window (dark theme, macOS), as custom elements.
3
+ // No dependencies, no image assets, no React. All inline styles + inline SVG.
4
+ // Defines: <browser-window>, <browser-tab-bar>, <browser-toolbar>, <browser-tab>, <browser-traffic-lights>
5
+ //
6
+ // Usage — wrap your page content in <browser-window> to get the tab bar + URL bar.
7
+ // Tabs are light-DOM children; the window marks the active one from `active-index`:
8
+ //
9
+ // <browser-window width="1100" height="680" url="acme.design/pricing" active-index="0">
10
+ // <browser-tab title="Pricing"></browser-tab>
11
+ // <browser-tab title="Docs"></browser-tab>
12
+ // ...your page content...
13
+ // </browser-window>
14
+ //
15
+ // Attributes:
16
+ // <browser-window> width (900) · height (600) · url ("example.com") · active-index (0)
17
+ // <browser-tab> title ("New Tab") · active
18
+ // <browser-toolbar> url
19
+ // With no <browser-tab> children the window renders a single "New Tab".
20
+ // `active` on a tab is for authoring; the parent rewrites data-active on every render,
21
+ // so when both are present the parent wins.
22
+ /* END USAGE */
23
+
24
+ (() => {
25
+ const C = {
26
+ barBg: '#202124',
27
+ tabBg: '#35363a',
28
+ text: '#e8eaed',
29
+ dim: '#9aa0a6',
30
+ urlBg: '#282a2d',
31
+ };
32
+ /**
33
+ * React version renders text and attributes through JSX, which escapes them. These
34
+ * elements build their shadow with innerHTML template strings, so that protection is
35
+ * gone — every value that comes from an author attribute, from persisted state, or
36
+ * from a Magic Share replay has to go through here. The artboard label matters most:
37
+ * it is renameable, it round-trips through the sidecar, AND a peer can push one in
38
+ * over Magic Share, so an unescaped one is script execution in the follower's page.
39
+ */
40
+ const esc = (v) =>
41
+ String(v ?? '').replace(
42
+ /[&<>"']/g,
43
+ (ch) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' })[ch],
44
+ );
45
+
46
+ // Non-numeric input falls through into a style="..." attribute, so it must be
47
+ // escaped too — a quote in it would break out of the attribute.
48
+ const px = (v) => (/^-?\d+(\.\d+)?$/.test(String(v)) ? `${v}px` : esc(v));
49
+
50
+ /**
51
+ * display:contents — the host generates no box of its own, so the shadow's root
52
+ * div becomes the parent's layout child directly. That is what keeps this port
53
+ * pixel-identical to the React version: the JSX component renders that same div
54
+ * with nothing wrapping it. `block` would add an outer box that swallows the
55
+ * flex properties meant for the inner one, showing up as a few pixels of drift
56
+ * in the visual regression.
57
+ */
58
+ /**
59
+ * The React version lives in the light DOM, so its box model comes from the
60
+ * product's own `*` reset (most products set border-box). That reset never
61
+ * reaches into a shadow root, and hardcoding either value gets half the products
62
+ * wrong. `inherit` is wrong too — it passes an inline border-box down to
63
+ * descendants, which a `*` rule would not. Copying the host's computed value is
64
+ * the only thing that matches the React version exactly: the host is a light DOM
65
+ * element, so the product's `*` rule does hit it.
66
+ */
67
+ const hostCss = (el) =>
68
+ '<style>:host{display:contents}*,*::before,*::after{box-sizing:' +
69
+ getComputedStyle(el).boxSizing +
70
+ '}</style>';
71
+
72
+ class Base extends HTMLElement {
73
+ constructor() {
74
+ super();
75
+ this.attachShadow({ mode: 'open' });
76
+ }
77
+ connectedCallback() {
78
+ this._render();
79
+ }
80
+ attributeChangedCallback() {
81
+ if (this.shadowRoot) this._render();
82
+ }
83
+ attr(name, fallback) {
84
+ const v = this.getAttribute(name);
85
+ return v === null || v === '' ? fallback : v;
86
+ }
87
+ }
88
+
89
+ class ChromeTrafficLights extends Base {
90
+ _render() {
91
+ const dot = (bg) =>
92
+ `<div style="width:12px;height:12px;border-radius:50%;background:${bg}"></div>`;
93
+ this.shadowRoot.innerHTML =
94
+ hostCss(this) +
95
+ `
96
+ <div part="frame" style="display:flex;gap:8px;padding:0 14px">
97
+ ${dot('#ff5f57')}${dot('#febc2e')}${dot('#28c840')}
98
+ </div>`;
99
+ }
100
+ }
101
+
102
+ class ChromeTab extends Base {
103
+ static get observedAttributes() {
104
+ return ['title', 'active', 'data-active'];
105
+ }
106
+ _render() {
107
+ // The parent rewrites data-active on every reflow; an author-written `active`
108
+ // only takes effect when there is no parent to override it.
109
+ const active = this.hasAttribute('data-active') || this.hasAttribute('active');
110
+ const curve = (flip) => `
111
+ <svg width="8" height="10" viewBox="0 0 8 10"
112
+ style="position:absolute;bottom:0;${flip ? 'right' : 'left'}:-8px;${flip ? 'transform:scaleX(-1);' : ''}">
113
+ <path d="M0 10C2 9 6 8 8 0V10H0Z" fill="${C.tabBg}"/>
114
+ </svg>`;
115
+ this.shadowRoot.innerHTML =
116
+ hostCss(this) +
117
+ `
118
+ <div part="frame" style="position:relative;height:34px;align-self:flex-end;padding:0 12px;
119
+ display:flex;align-items:center;gap:8px;
120
+ background:${active ? C.tabBg : 'transparent'};border-radius:8px 8px 0 0;
121
+ min-width:120px;max-width:220px;font-family:system-ui,sans-serif;font-size:12px;
122
+ color:${active ? C.text : C.dim}">
123
+ ${active ? curve(false) + curve(true) : ''}
124
+ <div style="width:14px;height:14px;border-radius:50%;background:#5f6368;flex-shrink:0"></div>
125
+ <span style="flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">${esc(this.attr('title', 'New Tab'))}</span>
126
+ </div>`;
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Project active-index onto data-active on the children.
132
+ *
133
+ * Same shape as how deck-stage marks its slides: list state is written by the
134
+ * parent onto light DOM children, so authors and tests can read it without
135
+ * piercing a shadow root.
136
+ */
137
+ function markTabs(host) {
138
+ const tabs = [...host.querySelectorAll(':scope > browser-tab')];
139
+ const idx = Number(host.getAttribute('active-index') ?? 0);
140
+ tabs.forEach((t, i) => t.toggleAttribute('data-active', i === idx));
141
+ return tabs;
142
+ }
143
+
144
+ /** Supply one default tab when the author wrote none, matching the React
145
+ * version's default `tabs` value. */
146
+ function ensureDefaultTab(host, slotName) {
147
+ if (host.querySelector(':scope > browser-tab')) return;
148
+ const t = document.createElement('browser-tab');
149
+ t.setAttribute('title', 'New Tab');
150
+ if (slotName) t.setAttribute('slot', slotName);
151
+ t.toggleAttribute('data-active', true);
152
+ host.appendChild(t);
153
+ }
154
+
155
+ class ChromeTabBar extends Base {
156
+ static get observedAttributes() {
157
+ return ['active-index'];
158
+ }
159
+ _render() {
160
+ this.shadowRoot.innerHTML =
161
+ hostCss(this) +
162
+ `
163
+ <div part="frame" style="display:flex;align-items:center;height:44px;background:${C.barBg};padding-right:8px">
164
+ <browser-traffic-lights></browser-traffic-lights>
165
+ <div style="display:flex;align-items:flex-end;height:100%;padding-left:4px;flex:1"><slot></slot></div>
166
+ </div>`;
167
+ ensureDefaultTab(this);
168
+ markTabs(this);
169
+ }
170
+ }
171
+
172
+ class ChromeToolbar extends Base {
173
+ static get observedAttributes() {
174
+ return ['url'];
175
+ }
176
+ _render() {
177
+ const dot = `<div style="width:28px;height:28px;display:flex;align-items:center;justify-content:center">
178
+ <div style="width:16px;height:16px;border-radius:50%;background:${C.dim};opacity:0.4"></div>
179
+ </div>`;
180
+ this.shadowRoot.innerHTML =
181
+ hostCss(this) +
182
+ `
183
+ <div part="frame" style="height:40px;background:${C.tabBg};display:flex;align-items:center;gap:4px;padding:0 8px">
184
+ ${dot}
185
+ <div style="flex:1;height:30px;border-radius:15px;background:${C.urlBg};display:flex;
186
+ align-items:center;gap:8px;padding:0 14px;margin:0 6px">
187
+ <div style="width:12px;height:12px;border-radius:50%;background:${C.dim};opacity:0.4"></div>
188
+ <span style="flex:1;color:${C.text};font-size:13px;font-family:system-ui,sans-serif">${esc(this.attr('url', 'example.com'))}</span>
189
+ </div>
190
+ ${dot}
191
+ </div>`;
192
+ }
193
+ }
194
+
195
+ class ChromeWindow extends Base {
196
+ static get observedAttributes() {
197
+ return ['width', 'height', 'url', 'active-index'];
198
+ }
199
+ connectedCallback() {
200
+ this._render();
201
+ if (!this._mo) {
202
+ // Re-mark after the author adds or removes a tab — same reason deck-stage
203
+ // watches for slide changes.
204
+ this._mo = new MutationObserver(() => this._markOwnTabs());
205
+ this._mo.observe(this, { childList: true });
206
+ }
207
+ }
208
+ disconnectedCallback() {
209
+ this._mo?.disconnect();
210
+ this._mo = null;
211
+ }
212
+ _markOwnTabs() {
213
+ for (const t of this.querySelectorAll(':scope > browser-tab')) t.setAttribute('slot', 'tab');
214
+ ensureDefaultTab(this, 'tab');
215
+ markTabs(this);
216
+ }
217
+ _render() {
218
+ this.shadowRoot.innerHTML =
219
+ hostCss(this) +
220
+ `
221
+ <div part="frame" style="width:${px(this.attr('width', 900))};height:${px(this.attr('height', 600))};
222
+ border-radius:10px;overflow:hidden;border:1px solid #DEE0E3;
223
+ display:flex;flex-direction:column;background:${C.tabBg}">
224
+ <div style="display:flex;align-items:center;height:44px;background:${C.barBg};padding-right:8px">
225
+ <browser-traffic-lights></browser-traffic-lights>
226
+ <div style="display:flex;align-items:flex-end;height:100%;padding-left:4px;flex:1">
227
+ <slot name="tab"></slot>
228
+ </div>
229
+ </div>
230
+ <browser-toolbar url="${esc(this.attr('url', 'example.com'))}"></browser-toolbar>
231
+ <div style="flex:1;background:#fff;overflow:auto"><slot></slot></div>
232
+ </div>`;
233
+ this._markOwnTabs();
234
+ }
235
+ }
236
+
237
+ for (const [tag, ctor] of [
238
+ ['browser-traffic-lights', ChromeTrafficLights],
239
+ ['browser-tab', ChromeTab],
240
+ ['browser-tab-bar', ChromeTabBar],
241
+ ['browser-toolbar', ChromeToolbar],
242
+ ['browser-window', ChromeWindow],
243
+ ]) {
244
+ if (!customElements.get(tag)) customElements.define(tag, ctor);
245
+ }
246
+ })();