You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/guide/features.md
+62-1Lines changed: 62 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -996,7 +996,12 @@ A `<slot name="X">` whose `X` matches a declared `<props>` key is a compile erro
996
996
997
997
## `:root { }` — the global escape hatch in scoped styles
998
998
999
-
`<style>` is scoped by default. Anything inside a `:root { }` selector is emitted globally — useful for CSS variables, font definitions, or anything else that legitimately belongs on the document:
999
+
`<style>` is scoped by default. The `:root { }` selector is the escape hatch, and it carries **two distinct capabilities** depending on what you put inside it:
1000
+
1001
+
1.**Flat custom-property declarations** (`:root { --var: … }`) → emitted globally as a top-level `:root` rule — for CSS variables, font definitions, or anything else that legitimately belongs on the document.
1002
+
2.**Nested selector rules** (`:root { .selector { … } }`) → the inner rules are emitted **bare/unscoped** (without Rozie's `[data-rozie-s-*]` scope attribute) so they can reach **engine-rendered runtime DOM** — the **engine-DOM escape hatch** (Phase 34).
1003
+
1004
+
### Flat custom properties — the global document layer
1000
1005
1001
1006
```rozie
1002
1007
<style>
@@ -1016,6 +1021,62 @@ A `<slot name="X">` whose `X` matches a declared `<props>` key is a compile erro
1016
1021
1017
1022
Each target picks the right escape hatch: Vue gets a sibling unscoped `<style>` block, Svelte gets `:global(:root)`, Angular gets `::ng-deep :root`, React/Solid get a separate `.global.css` file imported next to the module CSS, and Lit — whose `static styles` are shadow-DOM-scoped by default — gets the `:root` rules injected into the document via an `injectGlobalStyles` runtime call.
1018
1023
1024
+
### Nested selectors — the engine-DOM escape hatch
1025
+
1026
+
When you wrap a **selector rule** inside `:root { }` (rather than a flat custom property), Rozie emits that inner rule **bare and unscoped** — it does *not* get the component's `[data-rozie-s-<hash>]` scope attribute. This is the mechanism a wrapped vanilla-JS engine component needs to style the DOM the engine creates **at runtime**.
1027
+
1028
+
The problem it solves: when Rozie wraps an engine like CodeMirror, ProseMirror/TipTap, or flatpickr, that engine renders its own DOM nodes (`.cm-editor`/`.cm-scroller`, TipTap's `is-editor-empty` placeholder node, flatpickr's body-appended calendar). Those nodes are created by the engine *after* mount and **never carry Rozie's scope attribute** — so an ordinary scoped rule like `.cm-editor { … }` silently fails to match them on React/Solid/Lit (and is shadow-DOM-isolated on Lit). The nested-`:root` form lifts the rule out of scoping so it reaches engine DOM on **all six targets**, including through Lit's shadow boundary:
1029
+
1030
+
```rozie
1031
+
<style>
1032
+
/* Scoped to this component's own template elements. */
A real example from the TipTap wrapper styles the Placeholder extension's ghost text — the `is-editor-empty` node ProseMirror injects into an empty document:
Per-target emission of the nested rules mirrors the flat case but for selector rules rather than custom properties: React emits a `.global.css` sidecar, Vue an unscoped second `<style>` block, Svelte a `:global { … }` wrapper, Angular bare `::ng-deep`, Solid a `__rozieInjectStyle` head-inject, and Lit a **dual-sink** — the rules land in both `static styles` (for the shadow root) and `injectGlobalStyles` (for engine DOM that escapes the shadow boundary, e.g. a body-appended calendar).
1061
+
1062
+
This injection is intentionally **page-wide** — the rules go in as authored, with no anchoring or containment enforcement. If you want containment, scope the inner selectors under a wrapper class yourself (e.g. `:root { .my-editor .cm-editor { … } }`).
1063
+
1064
+
### `:global()` is forbidden (ROZ128)
1065
+
1066
+
You might reach for `:global(.cm-editor)` out of Vue/Svelte habit. **Don't** — it's a hard compile error (**ROZ128**). The `:global()` pseudo works natively *only* on Vue and Svelte (whose compilers understand it); on React, Solid, and Lit the browser sees an unknown pseudo and silently discards the entire rule. Rather than ship a selector that works on two of six targets and dies invisibly on three, Rozie blocks `:global()` in `<style>` selectors loudly and points you at the `:root { … }` engine-DOM escape hatch, which lowers to the same unscoped output on every target:
1067
+
1068
+
```rozie
1069
+
<style>
1070
+
/* ❌ ROZ128 — works on Vue/Svelte, silently dead on React/Solid/Lit. */
1071
+
:global(.cm-editor) { height: 100%; }
1072
+
1073
+
/* ✅ Canonical — bare/unscoped on all six targets. */
1074
+
:root {
1075
+
.cm-editor { height: 100%; }
1076
+
}
1077
+
</style>
1078
+
```
1079
+
1019
1080
## `:deep()` — reaching into child components from scoped styles
1020
1081
1021
1082
`:root` is the global escape hatch; `:deep()` is the **cross-component** one. Because `<style>` is scoped per component, a parent's selector like `.board > .rozie-sortable-list` can never match the child SortableList's rendered DOM — every component has its own scope attribute and the parent's selector goes looking for the parent's marker on the child's elements. `:deep(...)` lifts the inner selector out of the scope so it reaches the child's DOM directly:
0 commit comments