remove scopeStylesToRoot

This commit is contained in:
Timo K.
2026-10-01 13:55:39 +02:00
parent 81233e5cfb
commit 34577ccc0c
12 changed files with 450 additions and 185 deletions
+4 -5
View File
@@ -252,11 +252,10 @@ for the standalone app the root is the page, so they mean what the media queries
they replaced did. (The standalone-only views, such as the home and login pages,
still use plain media queries, since the component never shows them.)
The component's stylesheet is confined to the element it is mounted in: the
build rewrites every selector so that it matches only Element Call's root or
what is inside it, with `html`, `body` and `:root` standing for that root (see
`component/build/scopeStylesToRoot.ts`). A host's own page keeps its styles,
and Element Call brings its own fonts and design tokens along.
The component's stylesheet is confined to the element it is mounted in: its
base styles — element defaults and custom properties — are written against
Element Call's root element rather than the document, and everything else is a
CSS module. A host's own page keeps its styles.
The component speaks every language the app does. English is bundled in; the
other locales are split into chunks the host's bundler loads the first time
-112
View File
@@ -1,112 +0,0 @@
/*
Copyright 2026 Element Creations Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
import { describe, expect, it } from "vitest";
import postcss from "postcss";
import { ROOT_SELECTOR, scopeStylesToRoot } from "./scopeStylesToRoot";
const inRoot = `:where(${ROOT_SELECTOR}, ${ROOT_SELECTOR} *)`;
const isRoot = `:where(${ROOT_SELECTOR})`;
async function scope(css: string, file = "base.css"): Promise<string> {
const result = await postcss([scopeStylesToRoot()]).process(css, {
from: file,
});
return result.css;
}
describe("scopeStylesToRoot", () => {
it("makes the root stand in for the document", async () => {
expect(await scope("html { line-height: 1.15 }")).toBe(
`${isRoot} { line-height: 1.15 }`,
);
expect(await scope("body { margin: 0 }")).toBe(`${isRoot} { margin: 0 }`);
expect(await scope(":root { --a: 1 }")).toBe(`${isRoot} { --a: 1 }`);
expect(await scope("body.no-scroll-body { position: fixed }")).toBe(
`${isRoot}.no-scroll-body { position: fixed }`,
);
expect(await scope("body .x { color: red }")).toBe(
`${isRoot} .x { color: red }`,
);
});
it("collapses selectors that all became the root", async () => {
expect(await scope("html, body, input { font: inherit }")).toBe(
`${isRoot},input${inRoot} { font: inherit }`,
);
});
it("confines everything else to the root and what is inside it", async () => {
expect(await scope("h1 { margin: 0 }")).toBe(`h1${inRoot} { margin: 0 }`);
expect(await scope(".cpd-theme-dark { --a: 1 }")).toBe(
`.cpd-theme-dark${inRoot} { --a: 1 }`,
);
expect(await scope("* { box-sizing: border-box }")).toBe(
`*${inRoot} { box-sizing: border-box }`,
);
expect(await scope(".a > .b + .c { color: red }")).toBe(
`.a>.b+.c${inRoot} { color: red }`,
);
});
it("keeps pseudo-elements last", async () => {
expect(await scope("button::-moz-focus-inner { border: 0 }")).toBe(
`button${inRoot}::-moz-focus-inner { border: 0 }`,
);
expect(await scope(".a .b:hover::after { content: '' }")).toBe(
`.a .b:hover${inRoot}::after { content: '' }`,
);
expect(await scope("p:first-letter { color: red }")).toBe(
`p${inRoot}:first-letter { color: red }`,
);
});
it("leaves alone what already names the root", async () => {
const css = `${ROOT_SELECTOR}[data-platform="ios"] { --a: 1 }`;
expect(await scope(css)).toBe(css);
});
it("reaches into layers and media queries", async () => {
expect(
await scope(
"@layer normalize { h1 { margin: 0 } } @media (min-width: 1px) { p { margin: 0 } }",
),
).toBe(
`@layer normalize { h1${inRoot} { margin: 0 } } @media (min-width: 1px) { p${inRoot} { margin: 0 } }`,
);
});
it("does not touch keyframes or nested rules", async () => {
expect(
await scope("@keyframes spin { from { opacity: 0 } to { opacity: 1 } }"),
).toBe("@keyframes spin { from { opacity: 0 } to { opacity: 1 } }");
expect(
await scope(
".a { color: red; &:hover { color: blue } .b { color: green } }",
),
).toBe(
`.a${inRoot} { color: red; &:hover { color: blue } .b { color: green } }`,
);
});
it("only touches the bare selectors of a CSS module", async () => {
const file = "Settings.module.css";
expect(await scope("pre { font-size: 1px }", file)).toBe(
`pre${inRoot} { font-size: 1px }`,
);
expect(await scope(".modal pre { font-size: 1px }", file)).toBe(
`.modal pre${inRoot} { font-size: 1px }`,
);
expect(await scope(".box_abc12 { border: 0 }", file)).toBe(
".box_abc12 { border: 0 }",
);
expect(await scope(".a .b_abc12:hover { border: 0 }", file)).toBe(
".a .b_abc12:hover { border: 0 }",
);
});
});
+3 -2
View File
@@ -82,8 +82,9 @@ its caller? If not, cut it.
- Size against the root, not the window: `@container element-call (…)` and
`cqw` / `cqh`, never `@media (width)` or `vw` / `vh`. Media queries stay correct
only in standalone-only views — home, login.
- Style `[data-element-call-root]`, never `body` or `:root`; the component build
makes those stand for the root (`component/build/scopeStylesToRoot.ts`).
- Style `[data-element-call-root]`, never `body` or `:root`. A rule that matches
by element alone takes the root as a prefix (see `src/base.css`); nothing
rewrites selectors at build time, so a bare one reaches the host's page.
## Strings and a11y
-2
View File
@@ -33,8 +33,6 @@ export default {
"src/state/ServiceInterruptionsViewModel.ts",
],
ignoreDependencies: [
// Used in CSS
"normalize.css",
// Used for its global type declarations
"@types/grecaptcha",
"@types/sdp-transform",
-2
View File
@@ -106,14 +106,12 @@
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop",
"matrix-widget-api": "^1.18.0",
"node-stdlib-browser": "^1.3.1",
"normalize.css": "^8.0.1",
"oxfmt": "^0.66.0",
"oxlint": "^1.70.0",
"oxlint-tsgolint": "^0.25.0",
"pako": "^2.0.4",
"postcss": "^8.4.41",
"postcss-preset-env": "^10.0.0",
"postcss-selector-parser": "^7.1.1",
"posthog-js": "1.408.2",
"qrcode": "^1.5.4",
"react": "19",
-11
View File
@@ -199,9 +199,6 @@ importers:
node-stdlib-browser:
specifier: ^1.3.1
version: 1.3.1
normalize.css:
specifier: ^8.0.1
version: 8.0.1
oxfmt:
specifier: ^0.66.0
version: 0.66.0
@@ -220,9 +217,6 @@ importers:
postcss-preset-env:
specifier: ^10.0.0
version: 10.6.1(postcss@8.5.26)
postcss-selector-parser:
specifier: ^7.1.1
version: 7.1.3
posthog-js:
specifier: 1.408.2
version: 1.408.2
@@ -4784,9 +4778,6 @@ packages:
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
engines: {node: '>=0.10.0'}
normalize.css@8.0.1:
resolution: {integrity: sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg==}
npm-run-path@6.0.0:
resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==}
engines: {node: '>=18'}
@@ -10369,8 +10360,6 @@ snapshots:
normalize-path@3.0.0: {}
normalize.css@8.0.1: {}
npm-run-path@6.0.0:
dependencies:
path-key: 4.0.0
+40 -43
View File
@@ -10,14 +10,22 @@ and element defaults its own stylesheets build on top of.
Split out from index.css so that Element Call as a component can have these
without also being given the standalone page's layout, which would style the
host's own document. What remains here still speaks of the
document — normalize.css and the typography below use bare element selectors,
and the custom properties are declared on `:root` — which is right for the
page, and is why the component build rewrites it: there every selector is
confined to Element Call's root element (see component/build/scopeStylesToRoot.ts),
with `html`, `body` and `:root` becoming that element. Nothing needs to be
written differently here for that to work, but nothing here may rely on
reaching the host's document either.
host's own document. Compound's stylesheets — the design tokens, the component
styles and the fonts they use — are not here either: as a component, Element
Call shares the host's copy of Compound (it is a peer dependency, so that what
Compound floats into the host's body, tooltips and menus, is styled by the
host's stylesheet), and index.css loads it for the page.
Everything here is written against Element Call's root element, found by the
`data-element-call-root` attribute useTheme puts on it: the custom properties
are declared on it rather than on `:root`, and the element defaults — the
normalize.css next to this file, and the typography below — take it as a prefix
so that they reach only what is inside it. For the standalone page the root is
the body, so this means what the unscoped rules did; for a component it is the
container the host gave, and the host's document is left as it was. Nothing
rewrites selectors at build time, so a bare `h1` or `.theme` here would reach
the host's page in every build. CSS modules need none of this, since their
class names already confine them.
Nothing here should depend on where it lands relative to Element Call's
component stylesheets: the bundler decides that, and it decides differently for
@@ -32,11 +40,11 @@ the app and for the component build. */
@import url("@fontsource/inconsolata/400.css");
@import url("@fontsource/inconsolata/700.css");
@import url("normalize.css/normalize.css") layer(normalize);
@import url("./normalize.css") layer(normalize);
@import url("@vector-im/compound-design-tokens/assets/web/css/compound-design-tokens.css") layer(compound);
@import url("@vector-im/compound-web/dist/style.css") layer(compound.components);
:root {
[data-element-call-root] {
--font-scale: 1;
--font-size-micro: calc(10px * var(--font-scale));
--font-size-caption: calc(12px * var(--font-scale));
@@ -73,8 +81,8 @@ the app and for the component build. */
--call-view-header-footer-layer: 2;
}
:root,
[class*="cpd-theme-"] {
[data-element-call-root],
[data-element-call-root] [class*="cpd-theme-"] {
--video-tile-background: var(--cpd-color-bg-subtle-secondary);
}
@@ -93,7 +101,8 @@ before using one further down the tree. */
container: element-call / size;
}
.cpd-theme-dark {
[data-element-call-root].cpd-theme-dark,
[data-element-call-root] .cpd-theme-dark {
--cpd-color-border-accent: var(--cpd-color-green-1100);
--stopgap-color-on-solid-accent: var(--cpd-color-text-primary);
--stopgap-background-85: rgba(16, 19, 23, 0.85);
@@ -120,11 +129,6 @@ before using one further down the tree. */
}
}
/* We use this to not render the page at all until we know the theme.*/
.no-theme {
opacity: 0;
}
/* On Android and iOS, prefer native system fonts. The global.css file of
Compound Web is where these variables ultimately get consumed to set the page's
font-family. */
@@ -138,54 +142,45 @@ font-family. */
}
@layer compound-legacy {
h1,
h2,
h3,
h4,
h5,
h6,
p,
a {
[data-element-call-root] :is(h1, h2, h3, h4, h5, h6, p, a) {
margin-top: 0;
}
/* Headline Semi Bold */
h1 {
[data-element-call-root] h1 {
font-weight: 600;
font-size: var(--font-size-headline);
}
/* Title */
h2 {
[data-element-call-root] h2 {
font-weight: 600;
font-size: var(--font-size-title);
}
/* Subtitle */
h3 {
[data-element-call-root] h3 {
font-weight: 600;
font-size: var(--font-size-subtitle);
}
/* Body Semi Bold */
h4 {
[data-element-call-root] h4 {
font-weight: 600;
font-size: var(--font-size-body);
}
h1,
h2,
h3 {
[data-element-call-root] :is(h1, h2, h3) {
line-height: 1.2;
}
/* Body */
p {
[data-element-call-root] p {
font-size: var(--font-size-body);
line-height: var(--font-size-title);
}
hr {
[data-element-call-root] hr {
width: calc(100% - 24px);
border: none;
border-top: 1px solid var(--cpd-color-border-interactive-secondary);
@@ -199,25 +194,27 @@ font-family. */
margin: 0 12px;
}
summary {
[data-element-call-root] summary {
font-size: var(--font-size-body);
}
details > :not(summary) {
[data-element-call-root] details > :not(summary) {
margin-left: var(--font-size-body);
}
details[open] > summary {
[data-element-call-root] details[open] > summary {
margin-bottom: var(--font-size-body);
}
}
/* normalize.css sets the focus rings on buttons in Firefox to an unusual custom
outline, which is inconsistent with our other components and is not sufficiently
visible to be accessible. This resets it back to 'auto'. */
button:-moz-focusring,
[type="button"]:-moz-focusring,
[type="reset"]:-moz-focusring,
[type="submit"]:-moz-focusring {
visible to be accessible. This resets it back to 'auto'. The root is named in
`:where()`, which adds no specificity, so that the focus styles components set
with a class still win over this. */
:where([data-element-call-root]) button:-moz-focusring,
:where([data-element-call-root]) [type="button"]:-moz-focusring,
:where([data-element-call-root]) [type="reset"]:-moz-focusring,
:where([data-element-call-root]) [type="submit"]:-moz-focusring {
outline: auto;
}
+42
View File
@@ -0,0 +1,42 @@
/*
Copyright 2026 Element Creations Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
import { readFile } from "node:fs/promises";
import postcss, { type AtRule, type Rule } from "postcss";
import { describe, expect, it } from "vitest";
// As a component, Element Call's stylesheets share a document with the host's.
// CSS modules keep to themselves through their class names, but the base styles
// speak of bare elements and custom properties, and nothing rewrites them at
// build time: each rule has to name the root itself.
const ROOT = "[data-element-call-root]";
/** Whether a rule is relative to something other than the document. */
function isRelative(rule: Rule): boolean {
for (let parent = rule.parent; parent; parent = parent.parent) {
if (parent.type === "rule") return true;
if (parent.type === "atrule") {
const { name } = parent as AtRule;
if (name.endsWith("keyframes") || name === "page") return true;
}
}
return false;
}
describe.each(["src/base.css", "src/normalize.css"])("%s", (file) => {
it("confines every rule to Element Call's root", async () => {
const root = postcss.parse(await readFile(file, "utf8"), { from: file });
const unscoped: string[] = [];
root.walkRules((rule) => {
if (isRelative(rule)) return;
for (const selector of rule.selectors)
if (!selector.includes(ROOT))
unscoped.push(`${rule.source?.start?.line}: ${selector}`);
});
expect(unscoped).toEqual([]);
});
});
+7
View File
@@ -31,6 +31,13 @@ body.no-scroll-body {
width: 100%;
}
/* index.html starts the body with this class, so that nothing is painted until
useTheme has decided the theme and removed it. Only the page starts out hidden
like this; a host's container is shown whenever the host shows it. */
body.no-theme {
opacity: 0;
}
html,
body,
#root {
+354
View File
@@ -0,0 +1,354 @@
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
/* normalize.css, written against Element Call's root element instead of the
document. Upstream's rules are kept word for word, with `html` and `body`
replaced by `[data-element-call-root]` and every other selector prefixed with
it, so that as a component Element Call normalizes only what is inside the
container its host gave it. For the standalone page that container is the
body, so this means what the unscoped stylesheet did. Upstream's `body`
margin is left out: the page's margin is index.css's business, and a host's
container is not ours to reset.
Diff against upstream's normalize.css to see the changes. */
/* Document
========================================================================== */
/**
* 1. Correct the line height in all browsers.
* 2. Prevent adjustments of font size after orientation changes in iOS.
*/
[data-element-call-root] {
line-height: 1.15; /* 1 */
-webkit-text-size-adjust: 100%; /* 2 */
}
/* Sections
========================================================================== */
/**
* Render the `main` element consistently in IE.
*/
[data-element-call-root] main {
display: block;
}
/**
* Correct the font size and margin on `h1` elements within `section` and
* `article` contexts in Chrome, Firefox, and Safari.
*/
[data-element-call-root] h1 {
font-size: 2em;
margin: 0.67em 0;
}
/* Grouping content
========================================================================== */
/**
* 1. Add the correct box sizing in Firefox.
* 2. Show the overflow in Edge and IE.
*/
[data-element-call-root] hr {
box-sizing: content-box; /* 1 */
height: 0; /* 1 */
overflow: visible; /* 2 */
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/
[data-element-call-root] pre {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
}
/* Text-level semantics
========================================================================== */
/**
* Remove the gray background on active links in IE 10.
*/
[data-element-call-root] a {
background-color: transparent;
}
/**
* 1. Remove the bottom border in Chrome 57-
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
*/
[data-element-call-root] abbr[title] {
border-bottom: none; /* 1 */
text-decoration: underline; /* 2 */
text-decoration: underline dotted; /* 2 */
}
/**
* Add the correct font weight in Chrome, Edge, and Safari.
*/
[data-element-call-root] b,
[data-element-call-root] strong {
font-weight: bolder;
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/
[data-element-call-root] code,
[data-element-call-root] kbd,
[data-element-call-root] samp {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
}
/**
* Add the correct font size in all browsers.
*/
[data-element-call-root] small {
font-size: 80%;
}
/**
* Prevent `sub` and `sup` elements from affecting the line height in
* all browsers.
*/
[data-element-call-root] sub,
[data-element-call-root] sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
[data-element-call-root] sub {
bottom: -0.25em;
}
[data-element-call-root] sup {
top: -0.5em;
}
/* Embedded content
========================================================================== */
/**
* Remove the border on images inside links in IE 10.
*/
[data-element-call-root] img {
border-style: none;
}
/* Forms
========================================================================== */
/**
* 1. Change the font styles in all browsers.
* 2. Remove the margin in Firefox and Safari.
*/
[data-element-call-root] button,
[data-element-call-root] input,
[data-element-call-root] optgroup,
[data-element-call-root] select,
[data-element-call-root] textarea {
font-family: inherit; /* 1 */
font-size: 100%; /* 1 */
line-height: 1.15; /* 1 */
margin: 0; /* 2 */
}
/**
* Show the overflow in IE.
* 1. Show the overflow in Edge.
*/
[data-element-call-root] button,
[data-element-call-root] input {
/* 1 */
overflow: visible;
}
/**
* Remove the inheritance of text transform in Edge, Firefox, and IE.
* 1. Remove the inheritance of text transform in Firefox.
*/
[data-element-call-root] button,
[data-element-call-root] select {
/* 1 */
text-transform: none;
}
/**
* Correct the inability to style clickable types in iOS and Safari.
*/
[data-element-call-root] button,
[data-element-call-root] [type="button"],
[data-element-call-root] [type="reset"],
[data-element-call-root] [type="submit"] {
-webkit-appearance: button;
}
/**
* Remove the inner border and padding in Firefox.
*/
[data-element-call-root] button::-moz-focus-inner,
[data-element-call-root] [type="button"]::-moz-focus-inner,
[data-element-call-root] [type="reset"]::-moz-focus-inner,
[data-element-call-root] [type="submit"]::-moz-focus-inner {
border-style: none;
padding: 0;
}
/**
* Restore the focus styles unset by the previous rule.
*/
[data-element-call-root] button:-moz-focusring,
[data-element-call-root] [type="button"]:-moz-focusring,
[data-element-call-root] [type="reset"]:-moz-focusring,
[data-element-call-root] [type="submit"]:-moz-focusring {
outline: 1px dotted ButtonText;
}
/**
* Correct the padding in Firefox.
*/
[data-element-call-root] fieldset {
padding: 0.35em 0.75em 0.625em;
}
/**
* 1. Correct the text wrapping in Edge and IE.
* 2. Correct the color inheritance from `fieldset` elements in IE.
* 3. Remove the padding so developers are not caught out when they zero out
* `fieldset` elements in all browsers.
*/
[data-element-call-root] legend {
box-sizing: border-box; /* 1 */
color: inherit; /* 2 */
display: table; /* 1 */
max-width: 100%; /* 1 */
padding: 0; /* 3 */
white-space: normal; /* 1 */
}
/**
* Add the correct vertical alignment in Chrome, Firefox, and Opera.
*/
[data-element-call-root] progress {
vertical-align: baseline;
}
/**
* Remove the default vertical scrollbar in IE 10+.
*/
[data-element-call-root] textarea {
overflow: auto;
}
/**
* 1. Add the correct box sizing in IE 10.
* 2. Remove the padding in IE 10.
*/
[data-element-call-root] [type="checkbox"],
[data-element-call-root] [type="radio"] {
box-sizing: border-box; /* 1 */
padding: 0; /* 2 */
}
/**
* Correct the cursor style of increment and decrement buttons in Chrome.
*/
[data-element-call-root] [type="number"]::-webkit-inner-spin-button,
[data-element-call-root] [type="number"]::-webkit-outer-spin-button {
height: auto;
}
/**
* 1. Correct the odd appearance in Chrome and Safari.
* 2. Correct the outline style in Safari.
*/
[data-element-call-root] [type="search"] {
-webkit-appearance: textfield; /* 1 */
outline-offset: -2px; /* 2 */
}
/**
* Remove the inner padding in Chrome and Safari on macOS.
*/
[data-element-call-root] [type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
/**
* 1. Correct the inability to style clickable types in iOS and Safari.
* 2. Change font properties to `inherit` in Safari.
*/
[data-element-call-root] ::-webkit-file-upload-button {
-webkit-appearance: button; /* 1 */
font: inherit; /* 2 */
}
/* Interactive
========================================================================== */
/*
* Add the correct display in Edge, IE 10+, and Firefox.
*/
[data-element-call-root] details {
display: block;
}
/*
* Add the correct display in all browsers.
*/
[data-element-call-root] summary {
display: list-item;
}
/* Misc
========================================================================== */
/**
* Add the correct display in IE 10+.
*/
[data-element-call-root] template {
display: none;
}
/**
* Add the correct display in IE 10.
*/
[data-element-call-root] [hidden] {
display: none;
}
-4
View File
@@ -10,7 +10,6 @@ import { realpathSync } from "node:fs";
import * as fs from "node:fs";
import { vitePluginsConfig } from "./vite.config.ts";
import { scopeStylesToRoot } from "./component/build/scopeStylesToRoot";
// Serves the harness under `component/dev`, which embeds Element Call as a
// component the way a host application would. Development only: this is not
@@ -36,9 +35,6 @@ export default defineConfig(({ mode }) => {
return {
...vitePluginsConfig({ mode, html: false }),
root: "component/dev",
// The same scoping the library build applies, so the harness shows what a
// host will get — including whether its own page is left alone
css: { postcss: { plugins: [scopeStylesToRoot()] } },
// So that the harness can read the same config.json the standalone app
// does, if the developer has written one
publicDir: "../../public",
-4
View File
@@ -8,7 +8,6 @@ Please see LICENSE in the repository root for full details.
import { defineConfig } from "vite";
import { vitePluginsConfig } from "./vite.config.ts";
import { scopeStylesToRoot } from "./component/build/scopeStylesToRoot";
// Config for Element Call as a React component, to be imported by an
// application embedding it rather than served as a page of its own.
@@ -34,9 +33,6 @@ export default defineConfig(({ mode }) => {
// whatever is in `public` — including the developer's own config.json, which
// is not in the repository — into the output we would publish.
publicDir: false,
// A host's document is not ours to style: everything in the stylesheet is
// confined to the element Element Call is mounted in
css: { postcss: { plugins: [scopeStylesToRoot()] } },
build: {
// Into the package directory, so that `component/package.json` describes
// what sits next to it and the directory can be installed as a package