@cossackframework/renderer 0.7.1 → 0.7.5

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/README.md CHANGED
@@ -4,7 +4,9 @@ Cossack Renderer is a Lit-compatible rendering engine designed for **Light DOM**
4
4
 
5
5
  ## Installation
6
6
 
7
- This package is intended to use with the Cossack Framework via `create-cossack-app`, but can also be used standalone in any project that needs a lightweight rendering solution.
7
+ This package is included by projects created with `cossack create`, but can
8
+ also be used standalone in any project that needs a lightweight rendering
9
+ solution.
8
10
 
9
11
  ```bash
10
12
  pnpm add @cossackframework/renderer
@@ -129,6 +131,108 @@ import { unsafeHTML } from '@cossackframework/renderer';
129
131
  html`<div>${unsafeHTML('<script>...</script>')}</div>`
130
132
  ```
131
133
 
134
+ ### SVG Fragments
135
+
136
+ Use `svg` for fragments whose root nodes must be created in the SVG namespace.
137
+ SVG results can be nested, rendered in arrays, and used inside an HTML `<svg>`
138
+ element. The renderer switches back to the HTML namespace inside
139
+ `<foreignObject>`.
140
+
141
+ ```typescript
142
+ import { html, svg } from '@cossackframework/renderer';
143
+
144
+ const dot = (x: number, color: string) => svg`
145
+ <circle cx="${x}" cy="12" r="6" fill="${color}"></circle>
146
+ `;
147
+
148
+ html`<svg viewBox="0 0 100 24">${[dot(12, 'red'), dot(32, 'blue')]}</svg>`
149
+ ```
150
+
151
+ SVG and HTML templates serialize the same way during SSR. Namespace selection
152
+ is a client DOM concern and is preserved during hydration.
153
+
154
+ ### Rendering Nothing
155
+
156
+ `nothing` removes the value according to its binding context. An empty string
157
+ also renders no child node, but remains an ordinary value in attributes.
158
+
159
+ ```typescript
160
+ import { html, nothing } from '@cossackframework/renderer';
161
+
162
+ html`
163
+ <p>${showMessage ? message : nothing}</p>
164
+ <a title="prefix-${hasTitle ? title : nothing}">link</a>
165
+ <input .value=${hasValue ? value : nothing}>
166
+ <button ?disabled=${busy ? true : nothing}>Save</button>
167
+ <button @click=${enabled ? this.save : nothing}>Save</button>
168
+ <div ...=${enabled ? attributes : nothing}></div>
169
+ `
170
+ ```
171
+
172
+ In child expressions, `nothing` clears managed nodes. In normal attributes it
173
+ removes the whole attribute, including an attribute with several expressions.
174
+ Property bindings receive `undefined`, boolean attributes are removed, event
175
+ handlers are disabled, and a spread set to `nothing` removes its previously
176
+ managed values. SSR, hydration, and later updates use the same rules.
177
+
178
+ ## Component Styles
179
+
180
+ Declare Light DOM component styles with `static styles`. The `css` tag accepts
181
+ only numbers and nested `CSSResult` values in interpolations. Raw values require
182
+ the explicit `unsafeCSS` trust boundary.
183
+
184
+ ```typescript
185
+ import {
186
+ CossackElement,
187
+ css,
188
+ html,
189
+ unsafeCSS,
190
+ } from '@cossackframework/renderer';
191
+
192
+ const gap = 12;
193
+ const shared = css`.label { font-weight: 600; }`;
194
+ const reviewedThemeColor = unsafeCSS('rebeccapurple');
195
+
196
+ class Notice extends CossackElement {
197
+ static styles = [
198
+ shared,
199
+ css`
200
+ .notice { display: flex; gap: ${gap}px; color: ${reviewedThemeColor}; }
201
+ @media (width < 40rem) { .notice { display: block; } }
202
+ @keyframes enter { from { opacity: 0; } }
203
+ .notice { animation: enter 150ms; }
204
+ `,
205
+ ];
206
+
207
+ render() {
208
+ return html`<div class="notice"><span class="label">Ready</span></div>`;
209
+ }
210
+ }
211
+ ```
212
+
213
+ Style arrays may be nested. They are flattened in declaration order, and the
214
+ last occurrence of the same `CSSResult` wins. A subclass can extend inherited
215
+ styles explicitly:
216
+
217
+ ```typescript
218
+ class EmphasizedNotice extends Notice {
219
+ static styles = [Notice.styles, css`.notice { border: 2px solid currentColor; }`];
220
+ }
221
+ ```
222
+
223
+ Cossack scopes selectors by adding deterministic `data-cossack-scope`
224
+ attributes to elements created by that component and emits one managed style
225
+ element per component instance. Conditional at-rules and functional selectors
226
+ are scoped recursively; keyframe names and matching animation declarations are
227
+ rewritten. `:host`, `:host-context`, and `::slotted` are rejected because
228
+ Cossack uses Light DOM rather than Shadow DOM.
229
+
230
+ Scoping follows template ownership. Nested components use their own scope, while
231
+ projected templates retain the scope of the component that created them. This
232
+ is attribute-based isolation, not a Shadow DOM security boundary: inherited CSS
233
+ properties still inherit normally. `unsafeHTML` and manually supplied DOM nodes
234
+ are explicit escape hatches and are not guaranteed to receive scope attributes.
235
+
132
236
  ## Using Components in Templates
133
237
 
134
238
  Use the `component` helper function to include child components in your templates.
@@ -441,4 +545,4 @@ class Card extends CossackElement {
441
545
  `;
442
546
  }
443
547
  }
444
- ```
548
+ ```
@@ -4,5 +4,9 @@ export interface ComponentResult {
4
4
  clazz: new () => CossackElement;
5
5
  props: Record<string, unknown>;
6
6
  children: unknown;
7
+ /** Rendering owner captured when component() is evaluated. */
8
+ parent?: CossackElement | null;
9
+ /** Framework-private scope captured from the rendering owner. */
10
+ serviceScope?: unknown;
7
11
  }
8
12
  export declare const isComponentResult: (value: unknown) => value is ComponentResult;
@@ -1,5 +1,6 @@
1
1
  import { TemplateResult } from './cossack-html';
2
2
  import { Context } from './context';
3
+ import type { CSSResultGroup } from './css';
3
4
  export type PropertyDeclaration = {
4
5
  type?: unknown;
5
6
  reflect?: boolean;
@@ -28,6 +29,7 @@ export declare class CossackElement implements ReactiveControllerHost {
28
29
  static properties: PropertyDeclarations;
29
30
  static readonly _isCossackElement = true;
30
31
  static components: Record<string, typeof CossackElement>;
32
+ static styles?: CSSResultGroup;
31
33
  children: unknown;
32
34
  props: Record<string, unknown>;
33
35
  __parent: CossackElement | null;
@@ -56,6 +58,14 @@ export declare class CossackElement implements ReactiveControllerHost {
56
58
  private __notifyListeners;
57
59
  addRenderListener(listener: (template: TemplateResult | unknown | null) => void): void;
58
60
  removeRenderListener(listener: (template: TemplateResult | unknown | null) => void): void;
61
+ /** @internal Return the deterministic Light DOM style scope for this class. */
62
+ _getStyleScopeId(): string | undefined;
63
+ /**
64
+ * @internal Shared output finalizer used by standalone elements, nested
65
+ * component() rendering, and the core Cossack page/layout/app path.
66
+ */
67
+ _finalizeRenderOutput(value: TemplateResult | unknown | null): TemplateResult | unknown | null;
68
+ private _claimTemplateOwnership;
59
69
  mount(container: HTMLElement, hydrateFirst?: boolean): void;
60
70
  addEventListener(type: string, callback: EventListenerOrEventListenerObject | null, _options?: boolean | AddEventListenerOptions): void;
61
71
  removeEventListener(type: string, callback: EventListenerOrEventListenerObject | null, _options?: boolean | EventListenerOptions): void;