@kirana-style/angular 0.0.0-stage → 0.2.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.
@@ -0,0 +1,138 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema",
3
+ "title": "Dev Server Target",
4
+ "description": "Dev Server target options for Build Facade.",
5
+ "type": "object",
6
+ "properties": {
7
+ "buildTarget": {
8
+ "type": "string",
9
+ "description": "A build builder target to serve in the format of `project:target[:configuration]`. You can also pass in more than one configuration name as a comma-separated list. Example: `project:target:production,staging`.",
10
+ "pattern": "^[^:\\s]*:[^:\\s]*(:[^\\s]+)?$"
11
+ },
12
+ "port": {
13
+ "type": "number",
14
+ "description": "Port to listen on.",
15
+ "default": 4200
16
+ },
17
+ "host": {
18
+ "type": "string",
19
+ "description": "Host to listen on.",
20
+ "default": "localhost"
21
+ },
22
+ "proxyConfig": {
23
+ "type": "string",
24
+ "description": "Proxy configuration file. For more information, see https://angular.dev/tools/cli/serve#proxying-to-a-backend-server."
25
+ },
26
+ "ssl": {
27
+ "type": "boolean",
28
+ "description": "Serve using HTTPS.",
29
+ "default": false
30
+ },
31
+ "sslKey": {
32
+ "type": "string",
33
+ "description": "SSL key to use for serving HTTPS."
34
+ },
35
+ "sslCert": {
36
+ "type": "string",
37
+ "description": "SSL certificate to use for serving HTTPS."
38
+ },
39
+ "allowedHosts": {
40
+ "description": "The hosts that the development server will respond to. This option sets the Vite option of the same name. For further details: https://vite.dev/config/server-options.html#server-allowedhosts",
41
+ "default": [],
42
+ "oneOf": [
43
+ {
44
+ "type": "array",
45
+ "description": "A list of hosts that the development server will respond to.",
46
+ "items": {
47
+ "type": "string"
48
+ }
49
+ },
50
+ {
51
+ "type": "boolean",
52
+ "description": "Indicates that all hosts are allowed. This is not recommended and a security risk."
53
+ }
54
+ ]
55
+ },
56
+ "define": {
57
+ "description": "Defines global identifiers that will be replaced with a specified constant value when found in any JavaScript or TypeScript code including libraries. The value will be used directly. String values must be put in quotes. Identifiers within Angular metadata such as Component Decorators will not be replaced.",
58
+ "type": "object",
59
+ "additionalProperties": {
60
+ "type": "string"
61
+ }
62
+ },
63
+ "headers": {
64
+ "type": "object",
65
+ "description": "Custom HTTP headers to be added to all responses.",
66
+ "propertyNames": {
67
+ "pattern": "^[-_A-Za-z0-9]+$"
68
+ },
69
+ "additionalProperties": {
70
+ "type": "string"
71
+ }
72
+ },
73
+ "open": {
74
+ "type": "boolean",
75
+ "description": "Opens the url in default browser.",
76
+ "default": false,
77
+ "alias": "o"
78
+ },
79
+ "verbose": {
80
+ "type": "boolean",
81
+ "description": "Adds more details to output logging."
82
+ },
83
+ "liveReload": {
84
+ "type": "boolean",
85
+ "description": "Whether to reload the page on change, using live-reload.",
86
+ "default": true
87
+ },
88
+ "servePath": {
89
+ "type": "string",
90
+ "description": "The pathname where the application will be served."
91
+ },
92
+ "hmr": {
93
+ "type": "boolean",
94
+ "description": "Enable hot module replacement. Defaults to the value of 'liveReload'. Currently, only global and component stylesheets are supported."
95
+ },
96
+ "watch": {
97
+ "type": "boolean",
98
+ "description": "Rebuild on change.",
99
+ "default": true
100
+ },
101
+ "poll": {
102
+ "type": "number",
103
+ "description": "Enable and define the file watching poll time period in milliseconds."
104
+ },
105
+ "inspect": {
106
+ "default": false,
107
+ "description": "Activate debugging inspector. This option only has an effect when 'SSR' or 'SSG' are enabled.",
108
+ "oneOf": [
109
+ {
110
+ "type": "string",
111
+ "description": "Activate the inspector on host and port in the format of `[[host:]port]`. See the security warning in https://nodejs.org/docs/latest-v22.x/api/cli.html#warning-binding-inspector-to-a-public-ipport-combination-is-insecure regarding the host parameter usage."
112
+ },
113
+ { "type": "boolean" }
114
+ ]
115
+ },
116
+ "prebundle": {
117
+ "description": "Enable and control the Vite-based development server's prebundling capabilities. To enable prebundling, the Angular CLI cache must also be enabled.",
118
+ "default": true,
119
+ "oneOf": [
120
+ { "type": "boolean" },
121
+ {
122
+ "type": "object",
123
+ "properties": {
124
+ "exclude": {
125
+ "description": "List of package imports that should not be prebundled by the development server. The packages will be bundled into the application code itself. Note: specifying `@foo/bar` marks all paths within the `@foo/bar` package as excluded, including sub-paths like `@foo/bar/baz`.",
126
+ "type": "array",
127
+ "items": { "type": "string" }
128
+ }
129
+ },
130
+ "additionalProperties": false,
131
+ "required": ["exclude"]
132
+ }
133
+ ]
134
+ }
135
+ },
136
+ "additionalProperties": false,
137
+ "required": ["buildTarget"]
138
+ }
@@ -0,0 +1,2 @@
1
+ import { applicationBuilder } from "./builders.js";
2
+ export { applicationBuilder as default };
@@ -0,0 +1,6 @@
1
+ import { applicationBuilder } from "./builders.js";
2
+ //#region src/application.ts
3
+ /** `@kirana-style/angular:application`, loaded by the Angular CLI from builders.json. */
4
+ var application_default = applicationBuilder;
5
+ //#endregion
6
+ export { application_default as default };
@@ -0,0 +1,21 @@
1
+ import { Mirror } from "./mirror.js";
2
+ import { Builder, BuilderContext, BuilderOutput } from "@angular-devkit/architect";
3
+ import { ApplicationBuilderOptions, DevServerBuilderOptions } from "@angular/build";
4
+ //#region src/builders.d.ts
5
+ /**
6
+ * Application options with the mirror's replacements and the stylesheet added. The entry files
7
+ * (`browser`, `server`) are not replaced: Angular type-checks only the files its tsconfig lists, and
8
+ * an entry it reads from a replacement it would bundle unchecked. An entry holds no styles in an
9
+ * Angular app; the components it imports are replaced.
10
+ */
11
+ export declare function withMirror<Options extends {
12
+ fileReplacements?: unknown[];
13
+ styles?: unknown[];
14
+ browser?: unknown;
15
+ server?: unknown;
16
+ }>(options: Options, mirror: Mirror): Options;
17
+ export declare function application(options: ApplicationBuilderOptions, context: BuilderContext): AsyncIterable<BuilderOutput>;
18
+ export declare function devServer(options: DevServerBuilderOptions, context: BuilderContext): AsyncIterable<BuilderOutput>;
19
+ export declare const applicationBuilder: Builder;
20
+ export declare const devServerBuilder: Builder;
21
+ //#endregion
@@ -0,0 +1,69 @@
1
+ import { STYLESHEET, createMirror, watchMirror } from "./mirror.js";
2
+ import path from "node:path";
3
+ import { createBuilder } from "@angular-devkit/architect";
4
+ import { buildApplication, executeDevServerBuilder } from "@angular/build";
5
+ //#region src/builders.ts
6
+ /**
7
+ * The Angular CLI builders (`@kirana-style/angular:application`, `@kirana-style/angular:dev-server`): Angular's
8
+ * own, given Kirana's mirror (`mirror.ts`) as `fileReplacements` and its stylesheet in `styles`.
9
+ *
10
+ * "build": { "builder": "@kirana-style/angular:application", "options": { … as before … } }
11
+ * "serve": { "builder": "@kirana-style/angular:dev-server", … }
12
+ *
13
+ * The dev server is Angular's, run with a context whose build target's options carry the mirror:
14
+ * Angular reads those options itself and compiles them with its internal application build.
15
+ */
16
+ /** The project's source folder, from its configuration (`src` unless it says otherwise). */
17
+ async function sourceRootOf(context) {
18
+ const project = context.target?.project;
19
+ const metadata = project ? await context.getProjectMetadata(project) : {};
20
+ const root = typeof metadata.root === "string" ? metadata.root : "";
21
+ return typeof metadata.sourceRoot === "string" ? metadata.sourceRoot : path.join(root, "src");
22
+ }
23
+ /**
24
+ * Application options with the mirror's replacements and the stylesheet added. The entry files
25
+ * (`browser`, `server`) are not replaced: Angular type-checks only the files its tsconfig lists, and
26
+ * an entry it reads from a replacement it would bundle unchecked. An entry holds no styles in an
27
+ * Angular app; the components it imports are replaced.
28
+ */
29
+ function withMirror(options, mirror) {
30
+ const entries = new Set([options.browser, options.server].map((entry) => path.normalize(String(entry))));
31
+ return {
32
+ ...options,
33
+ fileReplacements: [...options.fileReplacements ?? [], ...mirror.replacements.filter((replacement) => !entries.has(path.normalize(replacement.replace)))],
34
+ styles: [...options.styles ?? [], STYLESHEET]
35
+ };
36
+ }
37
+ /** Prints the mirror's warnings and errors through the builder's logger (infos stay quiet). */
38
+ function report(context, mirror) {
39
+ for (const message of mirror.messages()) if (message.startsWith("error")) context.logger.error(message);
40
+ else if (message.startsWith("warning")) context.logger.warn(message);
41
+ }
42
+ async function* application(options, context) {
43
+ const sourceRoot = await sourceRootOf(context);
44
+ const mirror = await createMirror(context.workspaceRoot, sourceRoot, { minify: options.optimization !== false });
45
+ report(context, mirror);
46
+ const watcher = options.watch ? watchMirror(context.workspaceRoot, sourceRoot, mirror, () => report(context, mirror)) : void 0;
47
+ try {
48
+ yield* buildApplication(withMirror(options, mirror), context);
49
+ } finally {
50
+ watcher?.close();
51
+ }
52
+ }
53
+ async function* devServer(options, context) {
54
+ const sourceRoot = await sourceRootOf(context);
55
+ const mirror = await createMirror(context.workspaceRoot, sourceRoot);
56
+ report(context, mirror);
57
+ const watcher = watchMirror(context.workspaceRoot, sourceRoot, mirror, () => report(context, mirror));
58
+ const getTargetOptions = context.getTargetOptions.bind(context);
59
+ const wrapped = Object.assign(Object.create(context), { getTargetOptions: async (target) => withMirror(await getTargetOptions(target), mirror) });
60
+ try {
61
+ yield* executeDevServerBuilder(options, wrapped);
62
+ } finally {
63
+ watcher.close();
64
+ }
65
+ }
66
+ const applicationBuilder = createBuilder(application);
67
+ const devServerBuilder = createBuilder(devServer);
68
+ //#endregion
69
+ export { application, applicationBuilder, devServer, devServerBuilder, withMirror };
@@ -0,0 +1,2 @@
1
+ import { devServerBuilder } from "./builders.js";
2
+ export { devServerBuilder as default };
@@ -0,0 +1,6 @@
1
+ import { devServerBuilder } from "./builders.js";
2
+ //#region src/dev-server.ts
3
+ /** `@kirana-style/angular:dev-server`, loaded by the Angular CLI from builders.json. */
4
+ var dev_server_default = devServerBuilder;
5
+ //#endregion
6
+ export { dev_server_default as default };
@@ -0,0 +1,28 @@
1
+ import { Handle } from "@kirana-style/core/internal";
2
+ import * as i0 from "@angular/core";
3
+ import { Sx } from "@kirana-style/core";
4
+ //#region src/fesm2022/directive.d.ts
5
+ /** The class-name prefix (`names.prefix`); `k` unless the app provides another (`provideKirana`). */
6
+ export declare const KIRANA_PREFIX: i0.InjectionToken<string>;
7
+ /** Providers for an app whose `kirana.config` sets another class-name prefix. */
8
+ export declare function provideKirana(options: {
9
+ prefix: string;
10
+ }): i0.Provider[];
11
+ export declare class SxDirective implements i0.OnChanges {
12
+ #private;
13
+ /**
14
+ * The handles: one, or an array with falsy guards (`[styles.card, active && styles.on]`). Compiled
15
+ * handles are class strings (the Angular CLI builders type-check the compiled copy), so both are
16
+ * accepted.
17
+ */
18
+ sx: Sx | Handle;
19
+ ngOnChanges(): void;
20
+ static ɵfac: i0.ɵɵFactoryDeclaration<SxDirective, never>;
21
+ static ɵdir: i0.ɵɵDirectiveDeclaration<SxDirective, "[sx]", never, {
22
+ sx: {
23
+ alias: "sx";
24
+ required: false;
25
+ };
26
+ }, {}, never, never, true, never>;
27
+ }
28
+ //#endregion
@@ -0,0 +1,66 @@
1
+ import { applySx } from "../sx.js";
2
+ import * as i0 from "@angular/core";
3
+ //#region src/fesm2022/directive.ts
4
+ /**
5
+ * `[sx]` for Angular templates (D-068). A template reaches only its component's members, so `sx`
6
+ * cannot be compiled away there as it is in JSX and in Vue, Svelte or Astro templates: this
7
+ * directive merges the handles at runtime with Kirana's runtime instead, and keeps the element's own
8
+ * classes and styles.
9
+ *
10
+ * The class carries Angular's partial declarations (`ɵɵngDeclareFactory`, `ɵɵngDeclareDirective`),
11
+ * the form `ngc` emits for a library and Angular's linker compiles in every app build. They are
12
+ * written here rather than generated because this workspace's TypeScript (7) has no JavaScript API
13
+ * for `ngc` to run on; the Analog and Angular CLI examples build them in CI. The static types are the
14
+ * ones `ngc` would declare, so templates type-check `[sx]` against `Sx`. The file sits under
15
+ * `fesm2022/`, the Angular Package Format's folder, because Analog links only files on such a path.
16
+ */
17
+ /** The class-name prefix (`names.prefix`); `k` unless the app provides another (`provideKirana`). */
18
+ const KIRANA_PREFIX = new i0.InjectionToken("kirana prefix", {
19
+ providedIn: "root",
20
+ factory: () => "k"
21
+ });
22
+ /** Providers for an app whose `kirana.config` sets another class-name prefix. */
23
+ function provideKirana(options) {
24
+ return [{
25
+ provide: KIRANA_PREFIX,
26
+ useValue: options.prefix
27
+ }];
28
+ }
29
+ var SxDirective = class SxDirective {
30
+ /**
31
+ * The handles: one, or an array with falsy guards (`[styles.card, active && styles.on]`). Compiled
32
+ * handles are class strings (the Angular CLI builders type-check the compiled copy), so both are
33
+ * accepted.
34
+ */
35
+ sx;
36
+ #element = i0.inject(i0.ElementRef).nativeElement;
37
+ #renderer = i0.inject(i0.Renderer2);
38
+ #prefix = i0.inject(KIRANA_PREFIX);
39
+ #applied = {
40
+ classes: /* @__PURE__ */ new Set(),
41
+ styles: /* @__PURE__ */ new Set()
42
+ };
43
+ ngOnChanges() {
44
+ this.#applied = applySx(this.#renderer, this.#element, this.#applied, this.sx, this.#prefix);
45
+ }
46
+ static ɵfac = i0.ɵɵngDeclareFactory({
47
+ minVersion: "12.0.0",
48
+ version: "22.2.1",
49
+ ngImport: i0,
50
+ type: SxDirective,
51
+ deps: [],
52
+ target: i0.ɵɵFactoryTarget.Directive
53
+ });
54
+ static ɵdir = i0.ɵɵngDeclareDirective({
55
+ minVersion: "14.0.0",
56
+ version: "22.2.1",
57
+ type: SxDirective,
58
+ isStandalone: true,
59
+ selector: "[sx]",
60
+ inputs: { sx: "sx" },
61
+ usesOnChanges: true,
62
+ ngImport: i0
63
+ });
64
+ };
65
+ //#endregion
66
+ export { KIRANA_PREFIX, SxDirective, provideKirana };
package/src/index.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import { KIRANA_PREFIX, SxDirective, provideKirana } from "./fesm2022/directive.js";
2
+ import { Applied, Renderer, applySx, runtimeFor } from "./sx.js";
3
+ export { type Applied, KIRANA_PREFIX, type Renderer, SxDirective, applySx, provideKirana, runtimeFor };
package/src/index.js ADDED
@@ -0,0 +1,3 @@
1
+ import { applySx, runtimeFor } from "./sx.js";
2
+ import { KIRANA_PREFIX, SxDirective, provideKirana } from "./fesm2022/directive.js";
3
+ export { KIRANA_PREFIX, SxDirective, applySx, provideKirana, runtimeFor };
@@ -0,0 +1,28 @@
1
+ import { watch } from "node:fs";
2
+ //#region src/mirror.d.ts
3
+ /** Where the mirror lives, under the workspace root. */
4
+ export declare const MIRROR = ".kirana";
5
+ /** The stylesheet the builders add to `styles`. */
6
+ export declare const STYLESHEET: string;
7
+ /** Every file under `directory`, relative to it, without installed packages or hidden folders. */
8
+ export declare function walk(directory: string, prefix?: string): string[];
9
+ export type Mirror = {
10
+ /** `fileReplacements` for every TypeScript file of the source folder. */
11
+ replacements: {
12
+ replace: string;
13
+ with: string;
14
+ }[];
15
+ /** Mirrors one changed or deleted file (and what read from it); returns whether anything changed. */
16
+ update: (file: string) => boolean;
17
+ /** Diagnostics from the last pass, formatted. */
18
+ messages: () => string[];
19
+ };
20
+ /** Builds the mirror of `sourceRoot` (relative to `root`) and writes the stylesheet. */
21
+ export declare function createMirror(root: string, sourceRoot: string, options?: {
22
+ minify?: boolean;
23
+ }): Promise<Mirror>;
24
+ /** Mirrors every change under the source folder until closed (watch builds and the dev server). */
25
+ export declare function watchMirror(root: string, sourceRoot: string, mirror: Mirror, onUpdate: () => void, watch?: typeof watch): {
26
+ close: () => void;
27
+ };
28
+ //#endregion
package/src/mirror.js ADDED
@@ -0,0 +1,98 @@
1
+ import path from "node:path";
2
+ import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, watch, writeFileSync } from "node:fs";
3
+ import { Driver, formatDiagnostic } from "@kirana-style/compiler";
4
+ import { SKIPPED, shouldCompile } from "@kirana-style/compiler/host";
5
+ //#region src/mirror.ts
6
+ /**
7
+ * Kirana for the Angular CLI (D-068, `design-notes/angular.md`). Angular's application builder
8
+ * compiles TypeScript from its own program and offers no hook to change a source first, but it
9
+ * honours `fileReplacements`, by redirecting module resolution to the replacement file. So the
10
+ * builders keep a mirror of the source folder in `.kirana/`: every file copied, the ones that hold
11
+ * styles compiled by Kirana. A replacement's relative imports and `templateUrl`s then resolve inside
12
+ * the mirror, where every file exists. Every TypeScript file is replaced, the stylesheet is written
13
+ * beside the mirror, and in watch mode each change is mirrored again — Angular, which watches the
14
+ * replacements, rebuilds from there.
15
+ */
16
+ /** Where the mirror lives, under the workspace root. */
17
+ const MIRROR = ".kirana";
18
+ /** The stylesheet the builders add to `styles`. */
19
+ const STYLESHEET = path.join(MIRROR, "kirana.css");
20
+ /** Every file under `directory`, relative to it, without installed packages or hidden folders. */
21
+ function walk(directory, prefix = "") {
22
+ const out = [];
23
+ for (const entry of readdirSync(path.join(directory, prefix), { withFileTypes: true })) {
24
+ const relative = path.join(prefix, entry.name);
25
+ if (entry.isDirectory()) {
26
+ if (!SKIPPED.has(entry.name) && !entry.name.startsWith(".")) out.push(...walk(directory, relative));
27
+ } else out.push(relative);
28
+ }
29
+ return out;
30
+ }
31
+ const SCRIPT = /\.[cm]?tsx?$/;
32
+ /** Builds the mirror of `sourceRoot` (relative to `root`) and writes the stylesheet. */
33
+ async function createMirror(root, sourceRoot, options = {}) {
34
+ const driver = await Driver.create({ root });
35
+ const source = path.join(root, sourceRoot);
36
+ const target = path.join(root, MIRROR, sourceRoot);
37
+ const reported = [];
38
+ const texts = /* @__PURE__ */ new Map();
39
+ const mirror = (relative) => {
40
+ const from = path.join(source, relative);
41
+ const to = path.join(target, relative);
42
+ if (!existsSync(from)) {
43
+ driver.remove(from);
44
+ rmSync(to, { force: true });
45
+ return;
46
+ }
47
+ mkdirSync(path.dirname(to), { recursive: true });
48
+ const text = SCRIPT.test(relative) ? readFileSync(from, "utf8") : void 0;
49
+ if (text === void 0 || !shouldCompile(from, text, driver.libraries)) {
50
+ copyFileSync(from, to);
51
+ return;
52
+ }
53
+ const result = driver.transform(from, text);
54
+ texts.set(path.relative(root, from).replaceAll("\\", "/"), text);
55
+ reported.push(...result.diagnostics);
56
+ writeFileSync(to, result.code ?? text);
57
+ };
58
+ const stylesheet = () => {
59
+ reported.push(...driver.diagnostics());
60
+ const css = driver.stylesheet(options.minify ?? false);
61
+ const file = path.join(root, STYLESHEET);
62
+ const changed = !existsSync(file) || readFileSync(file, "utf8") !== css;
63
+ if (changed) writeFileSync(file, css);
64
+ return changed;
65
+ };
66
+ rmSync(target, {
67
+ recursive: true,
68
+ force: true
69
+ });
70
+ const files = walk(source);
71
+ for (const relative of files) mirror(relative);
72
+ stylesheet();
73
+ return {
74
+ replacements: files.filter((relative) => SCRIPT.test(relative) && !relative.endsWith(".d.ts")).map((relative) => ({
75
+ replace: path.join(sourceRoot, relative),
76
+ with: path.join(MIRROR, sourceRoot, relative)
77
+ })),
78
+ update(file) {
79
+ reported.length = 0;
80
+ const absolute = path.resolve(root, file);
81
+ const affected = [absolute, ...driver.invalidate(absolute)];
82
+ for (const changed of affected) mirror(path.relative(source, changed));
83
+ return stylesheet();
84
+ },
85
+ messages: () => reported.map((diagnostic) => formatDiagnostic(diagnostic, texts.get(diagnostic.file)))
86
+ };
87
+ }
88
+ /** Mirrors every change under the source folder until closed (watch builds and the dev server). */
89
+ function watchMirror(root, sourceRoot, mirror, onUpdate, watch$1 = watch) {
90
+ return watch$1(path.join(root, sourceRoot), { recursive: true }, (_, name) => {
91
+ const relative = String(name);
92
+ if (relative.split(/[\\/]/).some((part) => part.startsWith(".") || SKIPPED.has(part))) return;
93
+ mirror.update(path.join(sourceRoot, relative));
94
+ onUpdate();
95
+ });
96
+ }
97
+ //#endregion
98
+ export { MIRROR, STYLESHEET, createMirror, walk, watchMirror };
package/src/sx.d.ts ADDED
@@ -0,0 +1,21 @@
1
+ import { runtime } from "@kirana-style/core/internal";
2
+ //#region src/sx.d.ts
3
+ /** The part of Angular's `Renderer2` this needs. */
4
+ export type Renderer = {
5
+ addClass: (element: unknown, name: string) => void;
6
+ removeClass: (element: unknown, name: string) => void;
7
+ setStyle: (element: unknown, name: string, value: string, flags?: number) => void;
8
+ removeStyle: (element: unknown, name: string, flags?: number) => void;
9
+ };
10
+ /** What the last value applied: its classes and its custom properties. */
11
+ export type Applied = {
12
+ classes: Set<string>;
13
+ styles: Set<string>;
14
+ };
15
+ /** `RendererStyleFlags2.DashCase`: the name is a custom property, set as written. */
16
+ export declare const DASH_CASE = 2;
17
+ /** One runtime per class-name prefix, as each compiled module creates one. */
18
+ export declare function runtimeFor(prefix: string): ReturnType<typeof runtime>;
19
+ /** Applies `value` (a handle, or an array of handles and falsy guards); returns what is applied now. */
20
+ export declare function applySx(renderer: Renderer, element: unknown, previous: Applied, value: unknown, prefix: string): Applied;
21
+ //#endregion
package/src/sx.js ADDED
@@ -0,0 +1,37 @@
1
+ import { runtime } from "@kirana-style/core/internal";
2
+ //#region src/sx.ts
3
+ /**
4
+ * What `[sx]` does to an element, without Angular: merge the handles with Kirana's runtime (the same
5
+ * one compiled modules call), then bring the element's classes and custom properties from what the
6
+ * last value applied to what this one applies. Only what `[sx]` added is ever removed, so the
7
+ * element's own `class` and `style` stay.
8
+ */
9
+ /** `RendererStyleFlags2.DashCase`: the name is a custom property, set as written. */
10
+ const DASH_CASE = 2;
11
+ const runtimes = /* @__PURE__ */ new Map();
12
+ /** One runtime per class-name prefix, as each compiled module creates one. */
13
+ function runtimeFor(prefix) {
14
+ let created = runtimes.get(prefix);
15
+ if (!created) {
16
+ created = runtime(prefix, "class");
17
+ runtimes.set(prefix, created);
18
+ }
19
+ return created;
20
+ }
21
+ /** Applies `value` (a handle, or an array of handles and falsy guards); returns what is applied now. */
22
+ function applySx(renderer, element, previous, value, prefix) {
23
+ const handles = Array.isArray(value) ? value : [value];
24
+ const props = runtimeFor(prefix).sx(handles, [], []);
25
+ const classes = new Set((props.class ?? "").split(" ").filter(Boolean));
26
+ const styles = props.style ?? {};
27
+ for (const name of previous.classes) if (!classes.has(name)) renderer.removeClass(element, name);
28
+ for (const name of classes) if (!previous.classes.has(name)) renderer.addClass(element, name);
29
+ for (const name of previous.styles) if (!(name in styles)) renderer.removeStyle(element, name, 2);
30
+ for (const [name, setting] of Object.entries(styles)) renderer.setStyle(element, name, setting, 2);
31
+ return {
32
+ classes,
33
+ styles: new Set(Object.keys(styles))
34
+ };
35
+ }
36
+ //#endregion
37
+ export { DASH_CASE, applySx, runtimeFor };
package/src/vite.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ import { KiranaViteOptions } from "@kirana-style/vite";
2
+ import { Plugin } from "vite";
3
+ //#region src/vite.d.ts
4
+ export declare function kiranaAngular(options?: KiranaViteOptions): Plugin[];
5
+ //#endregion
6
+ export { type KiranaViteOptions as KiranaAngularOptions, kiranaAngular as default };
package/src/vite.js ADDED
@@ -0,0 +1,20 @@
1
+ import kirana from "@kirana-style/vite";
2
+ //#region src/vite.ts
3
+ /**
4
+ * Kirana for Analog, Angular on Vite (D-068). Angular compiles each TypeScript file from its own
5
+ * program on disk, so a plugin that ran first would change nothing it reads. This is Kirana's Vite
6
+ * plugin run after Angular's instead: it compiles the JavaScript Angular emitted for each module,
7
+ * where `css()` calls and their imports are as written. The stylesheet and its hot updates work as
8
+ * they do in every Vite app.
9
+ *
10
+ * plugins: [analog(), kiranaAngular()]
11
+ */
12
+ function kiranaAngular(options = {}) {
13
+ const [main, late] = kirana(options);
14
+ return [{
15
+ ...main,
16
+ enforce: "post"
17
+ }, late];
18
+ }
19
+ //#endregion
20
+ export { kiranaAngular as default, kiranaAngular };