@ng-native/icons 0.0.1 → 0.1.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ashley Hunter
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,50 @@
1
1
  # @ng-native/icons
2
2
 
3
- A placeholder: this version has no code. See https://ng-native.com.
3
+ Renders `@ng-icons` icon sets as real native shapes, through `NgIcon` and react-native-svg, instead
4
+ of the `innerHTML` a web app uses.
5
+
6
+ Alpha: APIs may change before 1.0.
7
+
8
+ ## Install
9
+
10
+ ```sh
11
+ npm install @ng-native/icons @ng-icons/core react-native-svg
12
+ npm install @ng-icons/heroicons # or whichever @ng-icons/* set you use
13
+ ```
14
+
15
+ ## Example
16
+
17
+ ```ts
18
+ import { Component } from '@angular/core';
19
+ import { NgIcon } from '@ng-native/icons';
20
+ import { provideIcons } from '@ng-icons/core';
21
+ import { heroBookOpen, heroAcademicCap } from '@ng-icons/heroicons/outline';
22
+
23
+ @Component({
24
+ selector: 'app-root',
25
+ imports: [NgIcon],
26
+ providers: [provideIcons({ heroBookOpen, heroAcademicCap })],
27
+ template: `<ng-icon name="heroBookOpen" [size]="28" color="#ff9f0a" />`,
28
+ })
29
+ export class App {}
30
+ ```
31
+
32
+ `provideIcons` works exactly as it does with `@ng-icons/core` on the web - only the rendering
33
+ underneath `NgIcon` changes.
34
+
35
+ ## What's in the package
36
+
37
+ - `NgIcon` - the component, taking `name` or `svg`, plus `size`, `color`, `strokeWidth` and
38
+ `accessibilityLabel`.
39
+ - A narrow SVG parser (`svg`, `g`, `path`, `circle`, `ellipse`, `rect`, `line`, `polyline`,
40
+ `polygon`) that covers every set ng-icons ships.
41
+
42
+ ## Docs
43
+
44
+ - [Icons](https://ng-native.com/packages/icons)
45
+ - [Root README](https://github.com/ng-native/ng-native/blob/main/README.md) and
46
+ [ARCHITECTURE.md](https://github.com/ng-native/ng-native/blob/main/ARCHITECTURE.md)
47
+
48
+ ## License
49
+
50
+ MIT
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Icons, from the `@ng-icons` packages a web app already uses.
3
+ *
4
+ * The icon sets are plain strings of SVG markup and `provideIcons` is ordinary DI, so both come
5
+ * across untouched; what this replaces is the rendering, which on the web is `innerHTML` and here
6
+ * is react-native-svg's native shapes.
7
+ */
8
+ export { NgIcon } from './ng-icon.ts';
9
+ export { type SvgNode } from './parse-svg.ts';
10
+ export { brushOf, nativeProps, viewBoxProps, type Brush, type SvgContext } from './svg-props.ts';
package/dist/index.js ADDED
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Icons, from the `@ng-icons` packages a web app already uses.
3
+ *
4
+ * The icon sets are plain strings of SVG markup and `provideIcons` is ordinary DI, so both come
5
+ * across untouched; what this replaces is the rendering, which on the web is `innerHTML` and here
6
+ * is react-native-svg's native shapes.
7
+ */
8
+ export { NgIcon } from "./ng-icon.js";
9
+ export {} from "./parse-svg.js";
10
+ export { brushOf, nativeProps, viewBoxProps } from "./svg-props.js";
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,EAAgB,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,YAAY,EAA+B,MAAM,gBAAgB,CAAC","sourcesContent":["/**\n * Icons, from the `@ng-icons` packages a web app already uses.\n *\n * The icon sets are plain strings of SVG markup and `provideIcons` is ordinary DI, so both come\n * across untouched; what this replaces is the rendering, which on the web is `innerHTML` and here\n * is react-native-svg's native shapes.\n */\nexport { NgIcon } from './ng-icon.ts';\nexport { type SvgNode } from './parse-svg.ts';\nexport { brushOf, nativeProps, viewBoxProps, type Brush, type SvgContext } from './svg-props.ts';\n"]}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * An icon, from the same `@ng-icons` packages a web app uses.
3
+ *
4
+ * An app imports `NgIcon`, provides the icons it uses with ng-icons' own `provideIcons`, and
5
+ * writes `<ng-icon name="heroBookOpen" [size]="28" color="#ff9f0a" />`. The imports are the same
6
+ * ones a web app writes:
7
+ *
8
+ * ```ts
9
+ * import { NgIcon } from '@ng-native/icons';
10
+ * import { provideIcons } from '@ng-icons/core';
11
+ * import { heroBookOpen } from '@ng-icons/heroicons/outline';
12
+ * ```
13
+ *
14
+ * The icon *packages* are the reusable part: each set ships its icons as plain strings of SVG
15
+ * markup, and `provideIcons` is ordinary dependency injection. What cannot be reused is
16
+ * `@ng-icons/core`'s own `NgIcon`, which inserts that markup with `innerHTML` and sizes it with
17
+ * CSS custom properties. This is the same component against react-native-svg's native views: the
18
+ * markup is parsed, translated, and committed as real shapes.
19
+ *
20
+ * The two things ng-icons expresses in CSS become inputs here. `color` is set on the host, which
21
+ * is what native paints a `currentColor` brush from - so a bound colour changes every stroke in
22
+ * the icon without re-parsing it. `strokeWidth` fills in `var(--ng-icon__stroke-width, 1.5)`,
23
+ * which is how the heroicons outline set carries its weight.
24
+ */
25
+ import { type Signal } from '@angular/core';
26
+ import * as i0 from "@angular/core";
27
+ export declare class NgIcon {
28
+ private readonly host;
29
+ private readonly renderer;
30
+ private readonly engine;
31
+ /** Every `provideIcons` in scope, innermost last, which is how ng-icons layers them. */
32
+ private readonly icons;
33
+ /** The key the icon was provided under: `heroBookOpen`, or `hero-book-open`. */
34
+ readonly name: import("@angular/core").InputSignal<string | undefined>;
35
+ /** Markup, for an icon that comes from somewhere other than a provider. */
36
+ readonly svg: import("@angular/core").InputSignal<string | undefined>;
37
+ /**
38
+ * Points, square. Icons are drawn from a `viewBox`, so this is the size on screen. A static
39
+ * `size="32"` is read as the number it spells.
40
+ */
41
+ readonly size: import("@angular/core").InputSignalWithTransform<number, string | number>;
42
+ /** What a `currentColor` stroke or fill paints as. Native resolves it, so binding is cheap. */
43
+ readonly color: import("@angular/core").InputSignal<string | undefined>;
44
+ /** Fills in `var(--ng-icon__stroke-width, …)`, which is how an outline set carries its weight. */
45
+ readonly strokeWidth: import("@angular/core").InputSignal<string | number | undefined>;
46
+ /** Absent leaves the icon out of the accessibility tree, which is right for a decorative one. */
47
+ readonly accessibilityLabel: import("@angular/core").InputSignal<string | undefined>;
48
+ protected readonly style: Signal<{
49
+ width: number;
50
+ height: number;
51
+ }>;
52
+ private readonly markup;
53
+ private readonly root;
54
+ protected readonly viewBox: Signal<Record<string, unknown>>;
55
+ constructor();
56
+ private draw;
57
+ private append;
58
+ private create;
59
+ static ɵfac: i0.ɵɵFactoryDeclaration<NgIcon, never>;
60
+ static ɵcmp: i0.ɵɵComponentDeclaration<NgIcon, "ng-icon", never, { "name": { "alias": "name"; "required": false; "isSignal": true; }; "svg": { "alias": "svg"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "color": { "alias": "color"; "required": false; "isSignal": true; }; "strokeWidth": { "alias": "strokeWidth"; "required": false; "isSignal": true; }; "accessibilityLabel": { "alias": "accessibilityLabel"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
61
+ }
@@ -0,0 +1,173 @@
1
+ /**
2
+ * An icon, from the same `@ng-icons` packages a web app uses.
3
+ *
4
+ * An app imports `NgIcon`, provides the icons it uses with ng-icons' own `provideIcons`, and
5
+ * writes `<ng-icon name="heroBookOpen" [size]="28" color="#ff9f0a" />`. The imports are the same
6
+ * ones a web app writes:
7
+ *
8
+ * ```ts
9
+ * import { NgIcon } from '@ng-native/icons';
10
+ * import { provideIcons } from '@ng-icons/core';
11
+ * import { heroBookOpen } from '@ng-icons/heroicons/outline';
12
+ * ```
13
+ *
14
+ * The icon *packages* are the reusable part: each set ships its icons as plain strings of SVG
15
+ * markup, and `provideIcons` is ordinary dependency injection. What cannot be reused is
16
+ * `@ng-icons/core`'s own `NgIcon`, which inserts that markup with `innerHTML` and sizes it with
17
+ * CSS custom properties. This is the same component against react-native-svg's native views: the
18
+ * markup is parsed, translated, and committed as real shapes.
19
+ *
20
+ * The two things ng-icons expresses in CSS become inputs here. `color` is set on the host, which
21
+ * is what native paints a `currentColor` brush from - so a bound colour changes every stroke in
22
+ * the icon without re-parsing it. `strokeWidth` fills in `var(--ng-icon__stroke-width, 1.5)`,
23
+ * which is how the heroicons outline set carries its weight.
24
+ */
25
+ import { Component, ElementRef, Renderer2, computed, effect, inject, input, numberAttribute, } from '@angular/core';
26
+ import { claimHost, HostEngine } from '@ng-native/fabric';
27
+ import { NgIconsToken } from '@ng-icons/core';
28
+ import { parseSvg } from "./parse-svg.js";
29
+ import { registerSvgComponents } from "./svg-elements.js";
30
+ import { SVG_ELEMENTS, nativeProps, styleAttributes, viewBoxProps } from "./svg-props.js";
31
+ import * as i0 from "@angular/core";
32
+ /** ng-icons stores icons under a camel-cased key, so `hero-book-open` finds `heroBookOpen`. */
33
+ function toPropertyName(name) {
34
+ return name
35
+ .replace(/([^a-zA-Z0-9])+(.)?/g, (_, __, character) => character ? character.toUpperCase() : '')
36
+ .replace(/[^a-zA-Z\d]/g, '')
37
+ .replace(/^([A-Z])/, (match) => match.toLowerCase());
38
+ }
39
+ export class NgIcon {
40
+ host = inject(ElementRef).nativeElement;
41
+ renderer = inject(Renderer2);
42
+ engine = inject(HostEngine);
43
+ /** Every `provideIcons` in scope, innermost last, which is how ng-icons layers them. */
44
+ icons = inject(NgIconsToken, { optional: true }) ?? [];
45
+ /** The key the icon was provided under: `heroBookOpen`, or `hero-book-open`. */
46
+ name = input(/* @ts-ignore */
47
+ ...(ngDevMode ? [undefined, { debugName: "name" }] : /* istanbul ignore next */ []));
48
+ /** Markup, for an icon that comes from somewhere other than a provider. */
49
+ svg = input(/* @ts-ignore */
50
+ ...(ngDevMode ? [undefined, { debugName: "svg" }] : /* istanbul ignore next */ []));
51
+ /**
52
+ * Points, square. Icons are drawn from a `viewBox`, so this is the size on screen. A static
53
+ * `size="32"` is read as the number it spells.
54
+ */
55
+ size = input(24, { ...(ngDevMode ? { debugName: "size" } : /* istanbul ignore next */ {}), transform: (value) => numberAttribute(value, 24) });
56
+ /** What a `currentColor` stroke or fill paints as. Native resolves it, so binding is cheap. */
57
+ color = input(/* @ts-ignore */
58
+ ...(ngDevMode ? [undefined, { debugName: "color" }] : /* istanbul ignore next */ []));
59
+ /** Fills in `var(--ng-icon__stroke-width, …)`, which is how an outline set carries its weight. */
60
+ strokeWidth = input(/* @ts-ignore */
61
+ ...(ngDevMode ? [undefined, { debugName: "strokeWidth" }] : /* istanbul ignore next */ []));
62
+ /** Absent leaves the icon out of the accessibility tree, which is right for a decorative one. */
63
+ accessibilityLabel = input(/* @ts-ignore */
64
+ ...(ngDevMode ? [undefined, { debugName: "accessibilityLabel" }] : /* istanbul ignore next */ []));
65
+ style = computed(() => ({ width: this.size(), height: this.size() }), /* @ts-ignore */
66
+ ...(ngDevMode ? [{ debugName: "style" }] : /* istanbul ignore next */ []));
67
+ markup = computed(() => {
68
+ const svg = this.svg();
69
+ if (svg !== undefined)
70
+ return svg;
71
+ const name = this.name();
72
+ if (name === undefined)
73
+ return undefined;
74
+ const key = toPropertyName(name);
75
+ // Reversed, because a nearer `provideIcons` should win over one further up the tree.
76
+ for (const set of [...this.icons].reverse()) {
77
+ if (set[key])
78
+ return set[key];
79
+ }
80
+ if (typeof ngDevMode !== 'undefined' && ngDevMode) {
81
+ console.warn(`[angular-native] no icon named ${name}. Pass it to provideIcons() from the set it ` +
82
+ `belongs to, the same way you would on the web.`);
83
+ }
84
+ return undefined;
85
+ }, /* @ts-ignore */
86
+ ...(ngDevMode ? [{ debugName: "markup" }] : /* istanbul ignore next */ []));
87
+ root = computed(() => {
88
+ const markup = this.markup();
89
+ return markup === undefined ? null : parseSvg(markup);
90
+ }, /* @ts-ignore */
91
+ ...(ngDevMode ? [{ debugName: "root" }] : /* istanbul ignore next */ []));
92
+ viewBox = computed(() => viewBoxProps(this.root()?.attrs['viewBox']), /* @ts-ignore */
93
+ ...(ngDevMode ? [{ debugName: "viewBox" }] : /* istanbul ignore next */ []));
94
+ constructor() {
95
+ // Before the first shape is created, which is before this component's own commit.
96
+ registerSvgComponents();
97
+ // Claimed, and stripped of the attributes Angular wrote from static inputs: `name` is one of
98
+ // ours *and* one of react-native-svg's, so leaving it there hands native an element name.
99
+ claimHost(this.host);
100
+ for (const input of Object.keys(NgIcon.ɵcmp?.inputs ?? {})) {
101
+ delete this.host.props[input];
102
+ }
103
+ // The shapes are created rather than templated: an icon's tree is data, and a template that
104
+ // could express it would be a component per tag with a recursive switch between them.
105
+ effect(() => this.draw());
106
+ }
107
+ draw() {
108
+ const root = this.root();
109
+ for (const child of [...this.host.children])
110
+ this.renderer.removeChild(this.host, child);
111
+ if (!root)
112
+ return;
113
+ // Everything hangs off one group carrying the root's presentation attributes, which is how
114
+ // react-native-svg's own `Svg` does it: the svg view itself holds no paint, so a root that
115
+ // says `stroke="currentColor"` needs a group for its children to inherit from.
116
+ const group = this.create('svg-g', root);
117
+ for (const child of root.children)
118
+ this.append(group, child);
119
+ this.renderer.appendChild(this.host, group);
120
+ }
121
+ append(parent, node) {
122
+ const element = SVG_ELEMENTS[node.tag];
123
+ if (!element)
124
+ return;
125
+ const shape = this.create(element, node);
126
+ for (const child of node.children)
127
+ this.append(shape, child);
128
+ this.renderer.appendChild(parent, shape);
129
+ }
130
+ create(element, node) {
131
+ const shape = this.renderer.createElement(element);
132
+ // Created here rather than by a template, so nothing else will account for it: without this
133
+ // the engine reports every shape as an element whose component was forgotten.
134
+ claimHost(shape);
135
+ const attrs = {
136
+ ...node.attrs,
137
+ // A `style` attribute is where ng-icons puts the stroke width, as a custom property.
138
+ ...styleAttributes(node.attrs['style'] ?? '', {
139
+ '--ng-icon__stroke-width': this.strokeWidth(),
140
+ }),
141
+ };
142
+ const props = nativeProps(node.tag, attrs, { color: (value) => this.engine.color(value) });
143
+ for (const [prop, value] of Object.entries(props)) {
144
+ this.renderer.setProperty(shape, prop, value);
145
+ }
146
+ return shape;
147
+ }
148
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: NgIcon, deps: [], target: i0.ɵɵFactoryTarget.Component });
149
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.2.0", type: NgIcon, isStandalone: true, selector: "ng-icon", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, svg: { classPropertyName: "svg", publicName: "svg", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, strokeWidth: { classPropertyName: "strokeWidth", publicName: "strokeWidth", isSignal: true, isRequired: false, transformFunction: null }, accessibilityLabel: { classPropertyName: "accessibilityLabel", publicName: "accessibilityLabel", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "style": "style()", "color": "color()", "bbWidth": "size()", "bbHeight": "size()", "minX": "viewBox().minX", "minY": "viewBox().minY", "vbWidth": "viewBox().vbWidth", "vbHeight": "viewBox().vbHeight", "align": "viewBox().align", "meetOrSlice": "viewBox().meetOrSlice", "accessible": "accessibilityLabel() !== undefined", "accessibilityRole": "accessibilityLabel() === undefined ? undefined : 'image'", "accessibilityLabel": "accessibilityLabel()" } }, ngImport: i0, template: '', isInline: true });
150
+ }
151
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: NgIcon, decorators: [{
152
+ type: Component,
153
+ args: [{
154
+ selector: 'ng-icon',
155
+ template: '',
156
+ host: {
157
+ '[style]': 'style()',
158
+ '[color]': 'color()',
159
+ '[bbWidth]': 'size()',
160
+ '[bbHeight]': 'size()',
161
+ '[minX]': 'viewBox().minX',
162
+ '[minY]': 'viewBox().minY',
163
+ '[vbWidth]': 'viewBox().vbWidth',
164
+ '[vbHeight]': 'viewBox().vbHeight',
165
+ '[align]': 'viewBox().align',
166
+ '[meetOrSlice]': 'viewBox().meetOrSlice',
167
+ '[accessible]': 'accessibilityLabel() !== undefined',
168
+ '[accessibilityRole]': "accessibilityLabel() === undefined ? undefined : 'image'",
169
+ '[accessibilityLabel]': 'accessibilityLabel()',
170
+ },
171
+ }]
172
+ }], ctorParameters: () => [], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], svg: [{ type: i0.Input, args: [{ isSignal: true, alias: "svg", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], strokeWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "strokeWidth", required: false }] }], accessibilityLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "accessibilityLabel", required: false }] }] } });
173
+ //# sourceMappingURL=ng-icon.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ng-icon.js","sourceRoot":"","sources":["../src/ng-icon.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * An icon, from the same `@ng-icons` packages a web app uses.\n *\n * An app imports `NgIcon`, provides the icons it uses with ng-icons' own `provideIcons`, and\n * writes `<ng-icon name=\"heroBookOpen\" [size]=\"28\" color=\"#ff9f0a\" />`. The imports are the same\n * ones a web app writes:\n *\n * ```ts\n * import { NgIcon } from '@ng-native/icons';\n * import { provideIcons } from '@ng-icons/core';\n * import { heroBookOpen } from '@ng-icons/heroicons/outline';\n * ```\n *\n * The icon *packages* are the reusable part: each set ships its icons as plain strings of SVG\n * markup, and `provideIcons` is ordinary dependency injection. What cannot be reused is\n * `@ng-icons/core`'s own `NgIcon`, which inserts that markup with `innerHTML` and sizes it with\n * CSS custom properties. This is the same component against react-native-svg's native views: the\n * markup is parsed, translated, and committed as real shapes.\n *\n * The two things ng-icons expresses in CSS become inputs here. `color` is set on the host, which\n * is what native paints a `currentColor` brush from - so a bound colour changes every stroke in\n * the icon without re-parsing it. `strokeWidth` fills in `var(--ng-icon__stroke-width, 1.5)`,\n * which is how the heroicons outline set carries its weight.\n */\nimport {\n Component,\n ElementRef,\n Renderer2,\n computed,\n effect,\n inject,\n input,\n numberAttribute,\n type Signal,\n} from '@angular/core';\nimport { claimHost, type EngineNode, HostEngine } from '@ng-native/fabric';\nimport { NgIconsToken } from '@ng-icons/core';\nimport { parseSvg, type SvgNode } from './parse-svg.ts';\nimport { registerSvgComponents } from './svg-elements.ts';\nimport { SVG_ELEMENTS, nativeProps, styleAttributes, viewBoxProps } from './svg-props.ts';\n\n/** ng-icons stores icons under a camel-cased key, so `hero-book-open` finds `heroBookOpen`. */\nfunction toPropertyName(name: string): string {\n return name\n .replace(/([^a-zA-Z0-9])+(.)?/g, (_, __, character: string | undefined) =>\n character ? character.toUpperCase() : '',\n )\n .replace(/[^a-zA-Z\\d]/g, '')\n .replace(/^([A-Z])/, (match) => match.toLowerCase());\n}\n\n@Component({\n selector: 'ng-icon',\n template: '',\n host: {\n '[style]': 'style()',\n '[color]': 'color()',\n '[bbWidth]': 'size()',\n '[bbHeight]': 'size()',\n '[minX]': 'viewBox().minX',\n '[minY]': 'viewBox().minY',\n '[vbWidth]': 'viewBox().vbWidth',\n '[vbHeight]': 'viewBox().vbHeight',\n '[align]': 'viewBox().align',\n '[meetOrSlice]': 'viewBox().meetOrSlice',\n '[accessible]': 'accessibilityLabel() !== undefined',\n '[accessibilityRole]': \"accessibilityLabel() === undefined ? undefined : 'image'\",\n '[accessibilityLabel]': 'accessibilityLabel()',\n },\n})\nexport class NgIcon {\n private readonly host = inject(ElementRef).nativeElement as EngineNode;\n private readonly renderer = inject(Renderer2);\n private readonly engine = inject(HostEngine);\n /** Every `provideIcons` in scope, innermost last, which is how ng-icons layers them. */\n private readonly icons = inject(NgIconsToken, { optional: true }) ?? [];\n\n /** The key the icon was provided under: `heroBookOpen`, or `hero-book-open`. */\n readonly name = input<string>();\n /** Markup, for an icon that comes from somewhere other than a provider. */\n readonly svg = input<string>();\n /**\n * Points, square. Icons are drawn from a `viewBox`, so this is the size on screen. A static\n * `size=\"32\"` is read as the number it spells.\n */\n readonly size = input(24, { transform: (value: number | string) => numberAttribute(value, 24) });\n /** What a `currentColor` stroke or fill paints as. Native resolves it, so binding is cheap. */\n readonly color = input<string>();\n /** Fills in `var(--ng-icon__stroke-width, …)`, which is how an outline set carries its weight. */\n readonly strokeWidth = input<number | string>();\n /** Absent leaves the icon out of the accessibility tree, which is right for a decorative one. */\n readonly accessibilityLabel = input<string>();\n\n protected readonly style = computed(() => ({ width: this.size(), height: this.size() }));\n\n private readonly markup: Signal<string | undefined> = computed(() => {\n const svg = this.svg();\n if (svg !== undefined) return svg;\n const name = this.name();\n if (name === undefined) return undefined;\n\n const key = toPropertyName(name);\n // Reversed, because a nearer `provideIcons` should win over one further up the tree.\n for (const set of [...this.icons].reverse()) {\n if (set[key]) return set[key];\n }\n if (typeof ngDevMode !== 'undefined' && ngDevMode) {\n console.warn(\n `[angular-native] no icon named ${name}. Pass it to provideIcons() from the set it ` +\n `belongs to, the same way you would on the web.`,\n );\n }\n return undefined;\n });\n\n private readonly root = computed(() => {\n const markup = this.markup();\n return markup === undefined ? null : parseSvg(markup);\n });\n\n protected readonly viewBox = computed(() => viewBoxProps(this.root()?.attrs['viewBox']));\n\n constructor() {\n // Before the first shape is created, which is before this component's own commit.\n registerSvgComponents();\n // Claimed, and stripped of the attributes Angular wrote from static inputs: `name` is one of\n // ours *and* one of react-native-svg's, so leaving it there hands native an element name.\n claimHost(this.host);\n for (const input of Object.keys(\n (NgIcon as { ɵcmp?: { inputs?: object } }).ɵcmp?.inputs ?? {},\n )) {\n delete this.host.props[input];\n }\n // The shapes are created rather than templated: an icon's tree is data, and a template that\n // could express it would be a component per tag with a recursive switch between them.\n effect(() => this.draw());\n }\n\n private draw(): void {\n const root = this.root();\n for (const child of [...this.host.children]) this.renderer.removeChild(this.host, child);\n if (!root) return;\n\n // Everything hangs off one group carrying the root's presentation attributes, which is how\n // react-native-svg's own `Svg` does it: the svg view itself holds no paint, so a root that\n // says `stroke=\"currentColor\"` needs a group for its children to inherit from.\n const group = this.create('svg-g', root);\n for (const child of root.children) this.append(group, child);\n this.renderer.appendChild(this.host, group);\n }\n\n private append(parent: unknown, node: SvgNode): void {\n const element = SVG_ELEMENTS[node.tag];\n if (!element) return;\n const shape = this.create(element, node);\n for (const child of node.children) this.append(shape, child);\n this.renderer.appendChild(parent, shape);\n }\n\n private create(element: string, node: SvgNode): unknown {\n const shape = this.renderer.createElement(element) as EngineNode;\n // Created here rather than by a template, so nothing else will account for it: without this\n // the engine reports every shape as an element whose component was forgotten.\n claimHost(shape);\n const attrs = {\n ...node.attrs,\n // A `style` attribute is where ng-icons puts the stroke width, as a custom property.\n ...styleAttributes(node.attrs['style'] ?? '', {\n '--ng-icon__stroke-width': this.strokeWidth(),\n }),\n };\n const props = nativeProps(node.tag, attrs, { color: (value) => this.engine.color(value) });\n for (const [prop, value] of Object.entries(props)) {\n this.renderer.setProperty(shape, prop, value);\n }\n return shape;\n }\n}\n"]}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * SVG markup to a tree, for the one kind of SVG an icon set ships.
3
+ *
4
+ * This is not a general XML parser and does not want to be. An icon is a build-time constant from
5
+ * a package like `@ng-icons/heroicons`: no namespaces to resolve, no entities, no CDATA, no
6
+ * external references, and across heroicons, lucide and bootstrap-icons - some five thousand
7
+ * icons - the entire tag surface is `svg`, `path`, `circle`, `rect`, `line`, `ellipse`,
8
+ * `polyline`, `polygon` and `g`.
9
+ *
10
+ * ponytail: a scanner rather than a parser. It ignores anything it does not recognise, which for
11
+ * a trusted constant is the right trade; the failure mode for markup outside that surface is a
12
+ * missing shape rather than a wrong one. An icon with a `<style>` block or a CSS class would need
13
+ * a real parser, and no set in ng-icons ships one.
14
+ */
15
+ export interface SvgNode {
16
+ readonly tag: string;
17
+ readonly attrs: Readonly<Record<string, string>>;
18
+ readonly children: SvgNode[];
19
+ }
20
+ /**
21
+ * The root element, or null when there is nothing renderable.
22
+ *
23
+ * Only elements are kept: an icon's text nodes are whitespace, and a `<title>` is a label a
24
+ * screen reader on the web would read, which here is `accessibilityLabel` on the host.
25
+ */
26
+ export declare function parseSvg(markup: string): SvgNode | null;
@@ -0,0 +1,58 @@
1
+ /**
2
+ * SVG markup to a tree, for the one kind of SVG an icon set ships.
3
+ *
4
+ * This is not a general XML parser and does not want to be. An icon is a build-time constant from
5
+ * a package like `@ng-icons/heroicons`: no namespaces to resolve, no entities, no CDATA, no
6
+ * external references, and across heroicons, lucide and bootstrap-icons - some five thousand
7
+ * icons - the entire tag surface is `svg`, `path`, `circle`, `rect`, `line`, `ellipse`,
8
+ * `polyline`, `polygon` and `g`.
9
+ *
10
+ * ponytail: a scanner rather than a parser. It ignores anything it does not recognise, which for
11
+ * a trusted constant is the right trade; the failure mode for markup outside that surface is a
12
+ * missing shape rather than a wrong one. An icon with a `<style>` block or a CSS class would need
13
+ * a real parser, and no set in ng-icons ships one.
14
+ */
15
+ /** `<tag`, `/>`, `</tag>` and the attribute soup between them. */
16
+ const TAG = /<\s*(\/)?\s*([a-zA-Z][\w:-]*)((?:[^>"']|"[^"]*"|'[^']*')*)(\/)?>/g;
17
+ const ATTRIBUTE = /([\w:-]+)\s*=\s*(?:"([^"]*)"|'([^']*)')/g;
18
+ /** Comments and processing instructions, which carry nothing a renderer needs. */
19
+ const IGNORED = /<!--[\s\S]*?-->|<\?[\s\S]*?\?>|<!\[CDATA\[[\s\S]*?\]\]>/g;
20
+ function attributesOf(source) {
21
+ const attrs = {};
22
+ ATTRIBUTE.lastIndex = 0;
23
+ let match;
24
+ while ((match = ATTRIBUTE.exec(source)) !== null) {
25
+ attrs[match[1]] = match[2] ?? match[3] ?? '';
26
+ }
27
+ return attrs;
28
+ }
29
+ /**
30
+ * The root element, or null when there is nothing renderable.
31
+ *
32
+ * Only elements are kept: an icon's text nodes are whitespace, and a `<title>` is a label a
33
+ * screen reader on the web would read, which here is `accessibilityLabel` on the host.
34
+ */
35
+ export function parseSvg(markup) {
36
+ const source = markup.replace(IGNORED, '');
37
+ const stack = [];
38
+ let root = null;
39
+ TAG.lastIndex = 0;
40
+ let match;
41
+ while ((match = TAG.exec(source)) !== null) {
42
+ const [, closing, tag, attributes, selfClosing] = match;
43
+ if (closing) {
44
+ stack.pop();
45
+ continue;
46
+ }
47
+ const node = { tag: tag, attrs: attributesOf(attributes ?? ''), children: [] };
48
+ stack[stack.length - 1]?.children.push(node);
49
+ root ??= node;
50
+ // The attribute run is greedy and swallows the `/` of `<path/>`, so the closing slash is
51
+ // recognised at either end of it. Miss this and every sibling nests inside the one before it,
52
+ // which draws the same icon and inherits paint that was never meant for it.
53
+ if (!selfClosing && !/\/\s*$/.test(attributes ?? ''))
54
+ stack.push(node);
55
+ }
56
+ return root;
57
+ }
58
+ //# sourceMappingURL=parse-svg.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse-svg.js","sourceRoot":"","sources":["../src/parse-svg.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAQH,kEAAkE;AAClE,MAAM,GAAG,GAAG,mEAAmE,CAAC;AAChF,MAAM,SAAS,GAAG,0CAA0C,CAAC;AAC7D,kFAAkF;AAClF,MAAM,OAAO,GAAG,0DAA0D,CAAC;AAE3E,SAAS,YAAY,CAAC,MAAc;IAClC,MAAM,KAAK,GAA2B,EAAE,CAAC;IACzC,SAAS,CAAC,SAAS,GAAG,CAAC,CAAC;IACxB,IAAI,KAA6B,CAAC;IAClC,OAAO,CAAC,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QACjD,KAAK,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAChD,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,QAAQ,CAAC,MAAc;IACrC,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IAC3C,MAAM,KAAK,GAAc,EAAE,CAAC;IAC5B,IAAI,IAAI,GAAmB,IAAI,CAAC;IAEhC,GAAG,CAAC,SAAS,GAAG,CAAC,CAAC;IAClB,IAAI,KAA6B,CAAC;IAClC,OAAO,CAAC,KAAK,GAAG,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAC3C,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,UAAU,EAAE,WAAW,CAAC,GAAG,KAAK,CAAC;QAExD,IAAI,OAAO,EAAE,CAAC;YACZ,KAAK,CAAC,GAAG,EAAE,CAAC;YACZ,SAAS;QACX,CAAC;QAED,MAAM,IAAI,GAAY,EAAE,GAAG,EAAE,GAAI,EAAE,KAAK,EAAE,YAAY,CAAC,UAAU,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;QACzF,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC7C,IAAI,KAAK,IAAI,CAAC;QACd,yFAAyF;QACzF,8FAA8F;QAC9F,4EAA4E;QAC5E,IAAI,CAAC,WAAW,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,UAAU,IAAI,EAAE,CAAC;YAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACzE,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC","sourcesContent":["/**\n * SVG markup to a tree, for the one kind of SVG an icon set ships.\n *\n * This is not a general XML parser and does not want to be. An icon is a build-time constant from\n * a package like `@ng-icons/heroicons`: no namespaces to resolve, no entities, no CDATA, no\n * external references, and across heroicons, lucide and bootstrap-icons - some five thousand\n * icons - the entire tag surface is `svg`, `path`, `circle`, `rect`, `line`, `ellipse`,\n * `polyline`, `polygon` and `g`.\n *\n * ponytail: a scanner rather than a parser. It ignores anything it does not recognise, which for\n * a trusted constant is the right trade; the failure mode for markup outside that surface is a\n * missing shape rather than a wrong one. An icon with a `<style>` block or a CSS class would need\n * a real parser, and no set in ng-icons ships one.\n */\n\nexport interface SvgNode {\n readonly tag: string;\n readonly attrs: Readonly<Record<string, string>>;\n readonly children: SvgNode[];\n}\n\n/** `<tag`, `/>`, `</tag>` and the attribute soup between them. */\nconst TAG = /<\\s*(\\/)?\\s*([a-zA-Z][\\w:-]*)((?:[^>\"']|\"[^\"]*\"|'[^']*')*)(\\/)?>/g;\nconst ATTRIBUTE = /([\\w:-]+)\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)')/g;\n/** Comments and processing instructions, which carry nothing a renderer needs. */\nconst IGNORED = /<!--[\\s\\S]*?-->|<\\?[\\s\\S]*?\\?>|<!\\[CDATA\\[[\\s\\S]*?\\]\\]>/g;\n\nfunction attributesOf(source: string): Record<string, string> {\n const attrs: Record<string, string> = {};\n ATTRIBUTE.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = ATTRIBUTE.exec(source)) !== null) {\n attrs[match[1]!] = match[2] ?? match[3] ?? '';\n }\n return attrs;\n}\n\n/**\n * The root element, or null when there is nothing renderable.\n *\n * Only elements are kept: an icon's text nodes are whitespace, and a `<title>` is a label a\n * screen reader on the web would read, which here is `accessibilityLabel` on the host.\n */\nexport function parseSvg(markup: string): SvgNode | null {\n const source = markup.replace(IGNORED, '');\n const stack: SvgNode[] = [];\n let root: SvgNode | null = null;\n\n TAG.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = TAG.exec(source)) !== null) {\n const [, closing, tag, attributes, selfClosing] = match;\n\n if (closing) {\n stack.pop();\n continue;\n }\n\n const node: SvgNode = { tag: tag!, attrs: attributesOf(attributes ?? ''), children: [] };\n stack[stack.length - 1]?.children.push(node);\n root ??= node;\n // The attribute run is greedy and swallows the `/` of `<path/>`, so the closing slash is\n // recognised at either end of it. Miss this and every sibling nests inside the one before it,\n // which draws the same icon and inherits paint that was never meant for it.\n if (!selfClosing && !/\\/\\s*$/.test(attributes ?? '')) stack.push(node);\n }\n\n return root;\n}\n"]}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Teach the engine the shape elements and both roots. Called by `NgIcon` and `ChartSvg`, so
3
+ * importing either component is the whole setup - the same lesson as `AnimatedStyle`, which
4
+ * stopped needing a bootstrap provider for the same reason. Exported for an app driving the
5
+ * shapes directly.
6
+ *
7
+ * A name the native side does not know renders as `UnimplementedNativeView` rather than erroring,
8
+ * so an app that has not installed `react-native-svg` sees blank boxes where its icons were.
9
+ */
10
+ export declare function registerSvgComponents(): void;
@@ -0,0 +1,57 @@
1
+ /**
2
+ * react-native-svg, driven directly.
3
+ *
4
+ * The same arrangement as react-native-screens in the router: the native side is a set of
5
+ * codegen'd Fabric components, and their React wrappers are unreachable, so the engine is taught
6
+ * the element names and the shapes are created with `createElement`. The app installs
7
+ * `react-native-svg` for the native half; none of its JavaScript is imported.
8
+ *
9
+ * No decorators here: tests import this at runtime.
10
+ */
11
+ import { nativePlatform, registerViewName } from '@ng-native/fabric';
12
+ /**
13
+ * Element name -> Fabric component. Lowercase, because the compiler drops a template with a
14
+ * capitalized element name, and prefixed, because `path` and
15
+ * `line` are words an app might reasonably want for a component of its own.
16
+ *
17
+ * The root is the one that differs per platform: iOS draws into `RNSVGSvgView`, Android into
18
+ * `RNSVGSvgViewAndroid`, and react-native-svg keeps them as separate specs because their props
19
+ * are not the same.
20
+ */
21
+ const SVG_VIEW_NAMES = {
22
+ 'svg-g': 'RNSVGGroup',
23
+ 'svg-path': 'RNSVGPath',
24
+ 'svg-circle': 'RNSVGCircle',
25
+ 'svg-ellipse': 'RNSVGEllipse',
26
+ 'svg-rect': 'RNSVGRect',
27
+ 'svg-line': 'RNSVGLine',
28
+ };
29
+ /**
30
+ * The roots: element name -> Fabric component, same iOS/Android split as the shapes above.
31
+ * `ng-icon` is `NgIcon`'s own host; a list because nothing stops two element names sharing one
32
+ * native view.
33
+ */
34
+ const SVG_ROOT_NAMES = ['ng-icon'];
35
+ let registered = false;
36
+ /**
37
+ * Teach the engine the shape elements and both roots. Called by `NgIcon` and `ChartSvg`, so
38
+ * importing either component is the whole setup - the same lesson as `AnimatedStyle`, which
39
+ * stopped needing a bootstrap provider for the same reason. Exported for an app driving the
40
+ * shapes directly.
41
+ *
42
+ * A name the native side does not know renders as `UnimplementedNativeView` rather than erroring,
43
+ * so an app that has not installed `react-native-svg` sees blank boxes where its icons were.
44
+ */
45
+ export function registerSvgComponents() {
46
+ if (registered)
47
+ return;
48
+ registered = true;
49
+ const rootViewName = nativePlatform() === 'android' ? 'RNSVGSvgViewAndroid' : 'RNSVGSvgView';
50
+ for (const root of SVG_ROOT_NAMES) {
51
+ registerViewName(root, rootViewName);
52
+ }
53
+ for (const [element, viewName] of Object.entries(SVG_VIEW_NAMES)) {
54
+ registerViewName(element, viewName);
55
+ }
56
+ }
57
+ //# sourceMappingURL=svg-elements.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"svg-elements.js","sourceRoot":"","sources":["../src/svg-elements.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAErE;;;;;;;;GAQG;AACH,MAAM,cAAc,GAA2B;IAC7C,OAAO,EAAE,YAAY;IACrB,UAAU,EAAE,WAAW;IACvB,YAAY,EAAE,aAAa;IAC3B,aAAa,EAAE,cAAc;IAC7B,UAAU,EAAE,WAAW;IACvB,UAAU,EAAE,WAAW;CACxB,CAAC;AAEF;;;;GAIG;AACH,MAAM,cAAc,GAAG,CAAC,SAAS,CAAC,CAAC;AAEnC,IAAI,UAAU,GAAG,KAAK,CAAC;AAEvB;;;;;;;;GAQG;AACH,MAAM,UAAU,qBAAqB;IACnC,IAAI,UAAU;QAAE,OAAO;IACvB,UAAU,GAAG,IAAI,CAAC;IAClB,MAAM,YAAY,GAAG,cAAc,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,cAAc,CAAC;IAC7F,KAAK,MAAM,IAAI,IAAI,cAAc,EAAE,CAAC;QAClC,gBAAgB,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;IACvC,CAAC;IACD,KAAK,MAAM,CAAC,OAAO,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,cAAc,CAAC,EAAE,CAAC;QACjE,gBAAgB,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IACtC,CAAC;AACH,CAAC","sourcesContent":["/**\n * react-native-svg, driven directly.\n *\n * The same arrangement as react-native-screens in the router: the native side is a set of\n * codegen'd Fabric components, and their React wrappers are unreachable, so the engine is taught\n * the element names and the shapes are created with `createElement`. The app installs\n * `react-native-svg` for the native half; none of its JavaScript is imported.\n *\n * No decorators here: tests import this at runtime.\n */\nimport { nativePlatform, registerViewName } from '@ng-native/fabric';\n\n/**\n * Element name -> Fabric component. Lowercase, because the compiler drops a template with a\n * capitalized element name, and prefixed, because `path` and\n * `line` are words an app might reasonably want for a component of its own.\n *\n * The root is the one that differs per platform: iOS draws into `RNSVGSvgView`, Android into\n * `RNSVGSvgViewAndroid`, and react-native-svg keeps them as separate specs because their props\n * are not the same.\n */\nconst SVG_VIEW_NAMES: Record<string, string> = {\n 'svg-g': 'RNSVGGroup',\n 'svg-path': 'RNSVGPath',\n 'svg-circle': 'RNSVGCircle',\n 'svg-ellipse': 'RNSVGEllipse',\n 'svg-rect': 'RNSVGRect',\n 'svg-line': 'RNSVGLine',\n};\n\n/**\n * The roots: element name -> Fabric component, same iOS/Android split as the shapes above.\n * `ng-icon` is `NgIcon`'s own host; a list because nothing stops two element names sharing one\n * native view.\n */\nconst SVG_ROOT_NAMES = ['ng-icon'];\n\nlet registered = false;\n\n/**\n * Teach the engine the shape elements and both roots. Called by `NgIcon` and `ChartSvg`, so\n * importing either component is the whole setup - the same lesson as `AnimatedStyle`, which\n * stopped needing a bootstrap provider for the same reason. Exported for an app driving the\n * shapes directly.\n *\n * A name the native side does not know renders as `UnimplementedNativeView` rather than erroring,\n * so an app that has not installed `react-native-svg` sees blank boxes where its icons were.\n */\nexport function registerSvgComponents(): void {\n if (registered) return;\n registered = true;\n const rootViewName = nativePlatform() === 'android' ? 'RNSVGSvgViewAndroid' : 'RNSVGSvgView';\n for (const root of SVG_ROOT_NAMES) {\n registerViewName(root, rootViewName);\n }\n for (const [element, viewName] of Object.entries(SVG_VIEW_NAMES)) {\n registerViewName(element, viewName);\n }\n}\n"]}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * SVG attributes to the props react-native-svg's native views actually take.
3
+ *
4
+ * They are not the same thing, and the difference is the whole reason this file exists. `fill`
5
+ * and `stroke` are not colours but *brushes*, a tagged struct that says whether the paint is a
6
+ * colour, the view's own `color`, or a reference to a gradient. `stroke-linecap`, `stroke-linejoin`
7
+ * and `fill-rule` are integers, not the keywords they are written as. And a shape has to declare
8
+ * which props it set - `propList` - because anything absent is inherited from the group above it,
9
+ * which is exactly how an icon whose root carries `stroke="currentColor"` paints its paths.
10
+ *
11
+ * Send the attributes through unchanged and every one of those reads as nothing at all, with no
12
+ * error: the shape draws in the default black, or does not draw.
13
+ *
14
+ * All of this is what react-native-svg does in JavaScript before its props reach native. Its
15
+ * components are React, so this is the same translation rather than a call into it.
16
+ */
17
+ /** The paint on a shape: a colour, the view's own colour, or a reference to a gradient. */
18
+ export type Brush = null | {
19
+ readonly type: 0;
20
+ readonly payload: unknown;
21
+ } | {
22
+ readonly type: 1;
23
+ readonly brushRef: string;
24
+ } | {
25
+ readonly type: 2;
26
+ };
27
+ /**
28
+ * Which element each SVG tag commits as. `polyline` and `polygon` are paths: react-native-svg has
29
+ * no component for either and converts their points to a `d`, so this does too.
30
+ */
31
+ export declare const SVG_ELEMENTS: Record<string, string>;
32
+ export interface SvgContext {
33
+ /** The host's colour conversion, which a brush payload is the result of. */
34
+ color(value: string): unknown;
35
+ }
36
+ export declare function brushOf(value: string, context: SvgContext): Brush;
37
+ /** `M x,y L x,y ...`, which is what a polyline or polygon is once native sees it. */
38
+ export declare function pointsToPath(points: string, close: boolean): string;
39
+ /** `stroke-width:2;opacity:.5` as attributes, with `var(--name, fallback)` resolved. */
40
+ export declare function styleAttributes(style: string, variables: Readonly<Record<string, string | number | undefined>>): Record<string, string>;
41
+ /** The four numbers of a `viewBox`, as the props the svg view takes them as. */
42
+ export declare function viewBoxProps(viewBox: string | undefined): Record<string, unknown>;
43
+ /**
44
+ * A `transform` attribute as a 2x3 matrix, which is the only form native takes.
45
+ *
46
+ * Rare in icon sets - one occurrence across bootstrap-icons and lucide, and that one is already a
47
+ * `matrix()` - but a shape drawn in the wrong place is worse than one not drawn, so the other
48
+ * three forms compose rather than being ignored.
49
+ */
50
+ export declare function transformMatrix(transform: string): number[] | undefined;
51
+ /**
52
+ * One element's native props.
53
+ *
54
+ * `propList` is the load-bearing part: it names the props this element set itself, and native
55
+ * inherits everything else from the group above. Omit it and a path with no `fill` of its own
56
+ * paints black instead of the colour its parent carries.
57
+ */
58
+ export declare function nativeProps(tag: string, attrs: Readonly<Record<string, string>>, context: SvgContext): Record<string, unknown>;
@@ -0,0 +1,248 @@
1
+ /**
2
+ * SVG attributes to the props react-native-svg's native views actually take.
3
+ *
4
+ * They are not the same thing, and the difference is the whole reason this file exists. `fill`
5
+ * and `stroke` are not colours but *brushes*, a tagged struct that says whether the paint is a
6
+ * colour, the view's own `color`, or a reference to a gradient. `stroke-linecap`, `stroke-linejoin`
7
+ * and `fill-rule` are integers, not the keywords they are written as. And a shape has to declare
8
+ * which props it set - `propList` - because anything absent is inherited from the group above it,
9
+ * which is exactly how an icon whose root carries `stroke="currentColor"` paints its paths.
10
+ *
11
+ * Send the attributes through unchanged and every one of those reads as nothing at all, with no
12
+ * error: the shape draws in the default black, or does not draw.
13
+ *
14
+ * All of this is what react-native-svg does in JavaScript before its props reach native. Its
15
+ * components are React, so this is the same translation rather than a call into it.
16
+ */
17
+ const LINECAP = { butt: 0, round: 1, square: 2 };
18
+ const LINEJOIN = { miter: 0, round: 1, bevel: 2 };
19
+ const FILL_RULE = { evenodd: 0, nonzero: 1 };
20
+ const URL_REFERENCE = /^url\(#(.+)\)$/;
21
+ /**
22
+ * Which element each SVG tag commits as. `polyline` and `polygon` are paths: react-native-svg has
23
+ * no component for either and converts their points to a `d`, so this does too.
24
+ */
25
+ export const SVG_ELEMENTS = {
26
+ g: 'svg-g',
27
+ path: 'svg-path',
28
+ circle: 'svg-circle',
29
+ ellipse: 'svg-ellipse',
30
+ rect: 'svg-rect',
31
+ line: 'svg-line',
32
+ polyline: 'svg-path',
33
+ polygon: 'svg-path',
34
+ };
35
+ /** Geometry, per tag. Everything else on a shape is presentation and shared. */
36
+ const GEOMETRY = {
37
+ path: ['d'],
38
+ circle: ['cx', 'cy', 'r'],
39
+ ellipse: ['cx', 'cy', 'rx', 'ry'],
40
+ rect: ['x', 'y', 'width', 'height', 'rx', 'ry'],
41
+ line: ['x1', 'y1', 'x2', 'y2'],
42
+ };
43
+ /** SVG attribute -> native prop, for the presentation attributes shapes and groups share. */
44
+ const PRESENTATION = {
45
+ fill: 'fill',
46
+ 'fill-opacity': 'fillOpacity',
47
+ 'fill-rule': 'fillRule',
48
+ stroke: 'stroke',
49
+ 'stroke-opacity': 'strokeOpacity',
50
+ 'stroke-width': 'strokeWidth',
51
+ 'stroke-linecap': 'strokeLinecap',
52
+ 'stroke-linejoin': 'strokeLinejoin',
53
+ 'stroke-dasharray': 'strokeDasharray',
54
+ 'stroke-dashoffset': 'strokeDashoffset',
55
+ 'stroke-miterlimit': 'strokeMiterlimit',
56
+ 'clip-rule': 'clipRule',
57
+ opacity: 'opacity',
58
+ };
59
+ export function brushOf(value, context) {
60
+ if (value === 'none')
61
+ return null;
62
+ // Deliberately kept as a brush rather than resolved here: native paints it from the view's own
63
+ // `color`, so a bound `[color]` on the icon changes every `currentColor` shape without a
64
+ // re-parse.
65
+ if (value === 'currentColor')
66
+ return { type: 2 };
67
+ const reference = URL_REFERENCE.exec(value);
68
+ if (reference)
69
+ return { type: 1, brushRef: reference[1] };
70
+ return { type: 0, payload: context.color(value) };
71
+ }
72
+ /** `M x,y L x,y ...`, which is what a polyline or polygon is once native sees it. */
73
+ export function pointsToPath(points, close) {
74
+ const numbers = points
75
+ .trim()
76
+ .split(/[\s,]+/)
77
+ .filter(Boolean);
78
+ let path = '';
79
+ for (let index = 0; index + 1 < numbers.length; index += 2) {
80
+ path += `${index === 0 ? 'M' : 'L'}${numbers[index]},${numbers[index + 1]}`;
81
+ }
82
+ return close && path ? `${path}Z` : path;
83
+ }
84
+ /** `stroke-width:2;opacity:.5` as attributes, with `var(--name, fallback)` resolved. */
85
+ export function styleAttributes(style, variables) {
86
+ const attrs = {};
87
+ for (const declaration of style.split(';')) {
88
+ const colon = declaration.indexOf(':');
89
+ if (colon === -1)
90
+ continue;
91
+ const name = declaration.slice(0, colon).trim();
92
+ const value = declaration.slice(colon + 1).trim();
93
+ const resolved = resolveVar(value, variables);
94
+ if (name && resolved !== undefined)
95
+ attrs[name] = resolved;
96
+ }
97
+ return attrs;
98
+ }
99
+ function resolveVar(value, variables) {
100
+ const match = /^var\(\s*(--[\w-]+)\s*(?:,\s*([^)]*))?\)$/.exec(value);
101
+ if (!match)
102
+ return value;
103
+ const supplied = variables[match[1]];
104
+ if (supplied !== undefined)
105
+ return String(supplied);
106
+ return match[2]?.trim() || undefined;
107
+ }
108
+ /** The four numbers of a `viewBox`, as the props the svg view takes them as. */
109
+ export function viewBoxProps(viewBox) {
110
+ if (!viewBox)
111
+ return {};
112
+ const parts = viewBox.trim().replace(/,/g, ' ').split(/\s+/).map(Number);
113
+ if (parts.length !== 4 || parts.some(Number.isNaN))
114
+ return {};
115
+ const [minX, minY, vbWidth, vbHeight] = parts;
116
+ return { minX, minY, vbWidth, vbHeight, align: 'xMidYMid', meetOrSlice: 0 };
117
+ }
118
+ const TRANSFORM = /([a-zA-Z]+)\s*\(([^)]*)\)/g;
119
+ /**
120
+ * A `transform` attribute as a 2x3 matrix, which is the only form native takes.
121
+ *
122
+ * Rare in icon sets - one occurrence across bootstrap-icons and lucide, and that one is already a
123
+ * `matrix()` - but a shape drawn in the wrong place is worse than one not drawn, so the other
124
+ * three forms compose rather than being ignored.
125
+ */
126
+ export function transformMatrix(transform) {
127
+ let matrix;
128
+ TRANSFORM.lastIndex = 0;
129
+ let match;
130
+ while ((match = TRANSFORM.exec(transform)) !== null) {
131
+ const values = match[2]
132
+ .trim()
133
+ .split(/[\s,]+/)
134
+ .map(Number);
135
+ const next = matrixFor(match[1], values);
136
+ if (next)
137
+ matrix = matrix ? multiply(matrix, next) : next;
138
+ }
139
+ return matrix;
140
+ }
141
+ /** `translate(4)` and `scale(2)` mean something for the missing argument; `rotate(90)` means 0. */
142
+ const at = (values, index, fallback) => values[index] ?? fallback;
143
+ function matrixFor(name, values) {
144
+ const first = at(values, 0, 0);
145
+ if (name === 'matrix')
146
+ return values.length === 6 ? values : undefined;
147
+ if (name === 'translate')
148
+ return [1, 0, 0, 1, first, at(values, 1, 0)];
149
+ if (name === 'scale')
150
+ return [first, 0, 0, at(values, 1, first), 0, 0];
151
+ if (name === 'rotate')
152
+ return rotation(first, at(values, 1, 0), at(values, 2, 0));
153
+ return undefined;
154
+ }
155
+ function rotation(degrees, cx, cy) {
156
+ const radians = (degrees * Math.PI) / 180;
157
+ const cos = Math.cos(radians);
158
+ const sin = Math.sin(radians);
159
+ // Rotating about a point is the rotation with the point moved to the origin and back.
160
+ return [cos, sin, -sin, cos, cx - cos * cx + sin * cy, cy - sin * cx - cos * cy];
161
+ }
162
+ /** `[a, b, c, d, tx, ty]`, in SVG's own order. */
163
+ function multiply(left, right) {
164
+ const [a1, b1, c1, d1, e1, f1] = left;
165
+ const [a2, b2, c2, d2, e2, f2] = right;
166
+ return [
167
+ a1 * a2 + c1 * b2,
168
+ b1 * a2 + d1 * b2,
169
+ a1 * c2 + c1 * d2,
170
+ b1 * c2 + d1 * d2,
171
+ a1 * e2 + c1 * f2 + e1,
172
+ b1 * e2 + d1 * f2 + f1,
173
+ ];
174
+ }
175
+ /**
176
+ * One element's native props.
177
+ *
178
+ * `propList` is the load-bearing part: it names the props this element set itself, and native
179
+ * inherits everything else from the group above. Omit it and a path with no `fill` of its own
180
+ * paints black instead of the colour its parent carries.
181
+ */
182
+ export function nativeProps(tag, attrs, context) {
183
+ const props = {};
184
+ const propList = [];
185
+ for (const [name, value] of Object.entries(attrs)) {
186
+ const prop = PRESENTATION[name];
187
+ if (!prop)
188
+ continue;
189
+ props[prop] = presentationValue(prop, value, context);
190
+ propList.push(prop);
191
+ }
192
+ if (propList.length > 0)
193
+ props['propList'] = propList;
194
+ for (const name of GEOMETRY[tag] ?? []) {
195
+ if (attrs[name] !== undefined)
196
+ props[name] = attrs[name];
197
+ }
198
+ if (tag === 'polyline' || tag === 'polygon') {
199
+ props['d'] = pointsToPath(attrs['points'] ?? '', tag === 'polygon');
200
+ }
201
+ if (attrs['transform']) {
202
+ const matrix = transformMatrix(attrs['transform']);
203
+ if (matrix)
204
+ props['matrix'] = matrix;
205
+ }
206
+ return props;
207
+ }
208
+ /**
209
+ * The props react-native-svg's Android view manager declares as `float` rather than `Dynamic`,
210
+ * with the default it applies when the prop is absent.
211
+ *
212
+ * `Dynamic` reads whatever arrives, so an SVG attribute can stay the string it was written as -
213
+ * which is what `strokeWidth` and every geometry attribute do. These five cannot: the Android
214
+ * manager takes a `float` parameter, and a string reaching one throws `java.lang.String cannot be
215
+ * cast to java.lang.Double` out of `setProperty` while the view is being created, which takes the
216
+ * whole screen down. iOS never noticed, because folly's `dynamic` coerces a numeric string on the
217
+ * way into a `Float` prop. react-native-svg's own JavaScript sends numbers here too, through
218
+ * `extractOpacity`, so this is the same translation and not an Android workaround.
219
+ */
220
+ const NUMERIC = {
221
+ opacity: 1,
222
+ fillOpacity: 1,
223
+ strokeOpacity: 1,
224
+ strokeDashoffset: 0,
225
+ strokeMiterlimit: 4,
226
+ };
227
+ function presentationValue(prop, value, context) {
228
+ if (prop === 'fill' || prop === 'stroke')
229
+ return brushOf(value, context);
230
+ if (prop === 'strokeLinecap')
231
+ return LINECAP[value] ?? 0;
232
+ if (prop === 'strokeLinejoin')
233
+ return LINEJOIN[value] ?? 0;
234
+ if (prop === 'fillRule' || prop === 'clipRule')
235
+ return FILL_RULE[value] ?? 1;
236
+ if (prop === 'strokeDasharray')
237
+ return value
238
+ .trim()
239
+ .split(/[\s,]+/)
240
+ .filter(Boolean);
241
+ return prop in NUMERIC ? numberFor(prop, value) : value;
242
+ }
243
+ /** The attribute as the float Android's view manager takes, or the default it applies. */
244
+ function numberFor(prop, value) {
245
+ const number = Number.parseFloat(value);
246
+ return Number.isNaN(number) ? NUMERIC[prop] : number;
247
+ }
248
+ //# sourceMappingURL=svg-props.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"svg-props.js","sourceRoot":"","sources":["../src/svg-props.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * SVG attributes to the props react-native-svg's native views actually take.\n *\n * They are not the same thing, and the difference is the whole reason this file exists. `fill`\n * and `stroke` are not colours but *brushes*, a tagged struct that says whether the paint is a\n * colour, the view's own `color`, or a reference to a gradient. `stroke-linecap`, `stroke-linejoin`\n * and `fill-rule` are integers, not the keywords they are written as. And a shape has to declare\n * which props it set - `propList` - because anything absent is inherited from the group above it,\n * which is exactly how an icon whose root carries `stroke=\"currentColor\"` paints its paths.\n *\n * Send the attributes through unchanged and every one of those reads as nothing at all, with no\n * error: the shape draws in the default black, or does not draw.\n *\n * All of this is what react-native-svg does in JavaScript before its props reach native. Its\n * components are React, so this is the same translation rather than a call into it.\n */\n\n/** The paint on a shape: a colour, the view's own colour, or a reference to a gradient. */\nexport type Brush =\n | null\n | { readonly type: 0; readonly payload: unknown }\n | { readonly type: 1; readonly brushRef: string }\n | { readonly type: 2 };\n\nconst LINECAP: Record<string, number> = { butt: 0, round: 1, square: 2 };\nconst LINEJOIN: Record<string, number> = { miter: 0, round: 1, bevel: 2 };\nconst FILL_RULE: Record<string, number> = { evenodd: 0, nonzero: 1 };\n\nconst URL_REFERENCE = /^url\\(#(.+)\\)$/;\n\n/**\n * Which element each SVG tag commits as. `polyline` and `polygon` are paths: react-native-svg has\n * no component for either and converts their points to a `d`, so this does too.\n */\nexport const SVG_ELEMENTS: Record<string, string> = {\n g: 'svg-g',\n path: 'svg-path',\n circle: 'svg-circle',\n ellipse: 'svg-ellipse',\n rect: 'svg-rect',\n line: 'svg-line',\n polyline: 'svg-path',\n polygon: 'svg-path',\n};\n\n/** Geometry, per tag. Everything else on a shape is presentation and shared. */\nconst GEOMETRY: Record<string, readonly string[]> = {\n path: ['d'],\n circle: ['cx', 'cy', 'r'],\n ellipse: ['cx', 'cy', 'rx', 'ry'],\n rect: ['x', 'y', 'width', 'height', 'rx', 'ry'],\n line: ['x1', 'y1', 'x2', 'y2'],\n};\n\n/** SVG attribute -> native prop, for the presentation attributes shapes and groups share. */\nconst PRESENTATION: Record<string, string> = {\n fill: 'fill',\n 'fill-opacity': 'fillOpacity',\n 'fill-rule': 'fillRule',\n stroke: 'stroke',\n 'stroke-opacity': 'strokeOpacity',\n 'stroke-width': 'strokeWidth',\n 'stroke-linecap': 'strokeLinecap',\n 'stroke-linejoin': 'strokeLinejoin',\n 'stroke-dasharray': 'strokeDasharray',\n 'stroke-dashoffset': 'strokeDashoffset',\n 'stroke-miterlimit': 'strokeMiterlimit',\n 'clip-rule': 'clipRule',\n opacity: 'opacity',\n};\n\nexport interface SvgContext {\n /** The host's colour conversion, which a brush payload is the result of. */\n color(value: string): unknown;\n}\n\nexport function brushOf(value: string, context: SvgContext): Brush {\n if (value === 'none') return null;\n // Deliberately kept as a brush rather than resolved here: native paints it from the view's own\n // `color`, so a bound `[color]` on the icon changes every `currentColor` shape without a\n // re-parse.\n if (value === 'currentColor') return { type: 2 };\n const reference = URL_REFERENCE.exec(value);\n if (reference) return { type: 1, brushRef: reference[1]! };\n return { type: 0, payload: context.color(value) };\n}\n\n/** `M x,y L x,y ...`, which is what a polyline or polygon is once native sees it. */\nexport function pointsToPath(points: string, close: boolean): string {\n const numbers = points\n .trim()\n .split(/[\\s,]+/)\n .filter(Boolean);\n let path = '';\n for (let index = 0; index + 1 < numbers.length; index += 2) {\n path += `${index === 0 ? 'M' : 'L'}${numbers[index]},${numbers[index + 1]}`;\n }\n return close && path ? `${path}Z` : path;\n}\n\n/** `stroke-width:2;opacity:.5` as attributes, with `var(--name, fallback)` resolved. */\nexport function styleAttributes(\n style: string,\n variables: Readonly<Record<string, string | number | undefined>>,\n): Record<string, string> {\n const attrs: Record<string, string> = {};\n for (const declaration of style.split(';')) {\n const colon = declaration.indexOf(':');\n if (colon === -1) continue;\n const name = declaration.slice(0, colon).trim();\n const value = declaration.slice(colon + 1).trim();\n const resolved = resolveVar(value, variables);\n if (name && resolved !== undefined) attrs[name] = resolved;\n }\n return attrs;\n}\n\nfunction resolveVar(\n value: string,\n variables: Readonly<Record<string, string | number | undefined>>,\n): string | undefined {\n const match = /^var\\(\\s*(--[\\w-]+)\\s*(?:,\\s*([^)]*))?\\)$/.exec(value);\n if (!match) return value;\n const supplied = variables[match[1]!];\n if (supplied !== undefined) return String(supplied);\n return match[2]?.trim() || undefined;\n}\n\n/** The four numbers of a `viewBox`, as the props the svg view takes them as. */\nexport function viewBoxProps(viewBox: string | undefined): Record<string, unknown> {\n if (!viewBox) return {};\n const parts = viewBox.trim().replace(/,/g, ' ').split(/\\s+/).map(Number);\n if (parts.length !== 4 || parts.some(Number.isNaN)) return {};\n const [minX, minY, vbWidth, vbHeight] = parts;\n return { minX, minY, vbWidth, vbHeight, align: 'xMidYMid', meetOrSlice: 0 };\n}\n\nconst TRANSFORM = /([a-zA-Z]+)\\s*\\(([^)]*)\\)/g;\n\n/**\n * A `transform` attribute as a 2x3 matrix, which is the only form native takes.\n *\n * Rare in icon sets - one occurrence across bootstrap-icons and lucide, and that one is already a\n * `matrix()` - but a shape drawn in the wrong place is worse than one not drawn, so the other\n * three forms compose rather than being ignored.\n */\nexport function transformMatrix(transform: string): number[] | undefined {\n let matrix: number[] | undefined;\n TRANSFORM.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = TRANSFORM.exec(transform)) !== null) {\n const values = match[2]!\n .trim()\n .split(/[\\s,]+/)\n .map(Number);\n const next = matrixFor(match[1]!, values);\n if (next) matrix = matrix ? multiply(matrix, next) : next;\n }\n return matrix;\n}\n\n/** `translate(4)` and `scale(2)` mean something for the missing argument; `rotate(90)` means 0. */\nconst at = (values: number[], index: number, fallback: number) => values[index] ?? fallback;\n\nfunction matrixFor(name: string, values: number[]): number[] | undefined {\n const first = at(values, 0, 0);\n if (name === 'matrix') return values.length === 6 ? values : undefined;\n if (name === 'translate') return [1, 0, 0, 1, first, at(values, 1, 0)];\n if (name === 'scale') return [first, 0, 0, at(values, 1, first), 0, 0];\n if (name === 'rotate') return rotation(first, at(values, 1, 0), at(values, 2, 0));\n return undefined;\n}\n\nfunction rotation(degrees: number, cx: number, cy: number): number[] {\n const radians = (degrees * Math.PI) / 180;\n const cos = Math.cos(radians);\n const sin = Math.sin(radians);\n // Rotating about a point is the rotation with the point moved to the origin and back.\n return [cos, sin, -sin, cos, cx - cos * cx + sin * cy, cy - sin * cx - cos * cy];\n}\n\n/** `[a, b, c, d, tx, ty]`, in SVG's own order. */\nfunction multiply(left: number[], right: number[]): number[] {\n const [a1, b1, c1, d1, e1, f1] = left as [number, number, number, number, number, number];\n const [a2, b2, c2, d2, e2, f2] = right as [number, number, number, number, number, number];\n return [\n a1 * a2 + c1 * b2,\n b1 * a2 + d1 * b2,\n a1 * c2 + c1 * d2,\n b1 * c2 + d1 * d2,\n a1 * e2 + c1 * f2 + e1,\n b1 * e2 + d1 * f2 + f1,\n ];\n}\n\n/**\n * One element's native props.\n *\n * `propList` is the load-bearing part: it names the props this element set itself, and native\n * inherits everything else from the group above. Omit it and a path with no `fill` of its own\n * paints black instead of the colour its parent carries.\n */\nexport function nativeProps(\n tag: string,\n attrs: Readonly<Record<string, string>>,\n context: SvgContext,\n): Record<string, unknown> {\n const props: Record<string, unknown> = {};\n const propList: string[] = [];\n\n for (const [name, value] of Object.entries(attrs)) {\n const prop = PRESENTATION[name];\n if (!prop) continue;\n props[prop] = presentationValue(prop, value, context);\n propList.push(prop);\n }\n if (propList.length > 0) props['propList'] = propList;\n\n for (const name of GEOMETRY[tag] ?? []) {\n if (attrs[name] !== undefined) props[name] = attrs[name];\n }\n if (tag === 'polyline' || tag === 'polygon') {\n props['d'] = pointsToPath(attrs['points'] ?? '', tag === 'polygon');\n }\n if (attrs['transform']) {\n const matrix = transformMatrix(attrs['transform']);\n if (matrix) props['matrix'] = matrix;\n }\n return props;\n}\n\n/**\n * The props react-native-svg's Android view manager declares as `float` rather than `Dynamic`,\n * with the default it applies when the prop is absent.\n *\n * `Dynamic` reads whatever arrives, so an SVG attribute can stay the string it was written as -\n * which is what `strokeWidth` and every geometry attribute do. These five cannot: the Android\n * manager takes a `float` parameter, and a string reaching one throws `java.lang.String cannot be\n * cast to java.lang.Double` out of `setProperty` while the view is being created, which takes the\n * whole screen down. iOS never noticed, because folly's `dynamic` coerces a numeric string on the\n * way into a `Float` prop. react-native-svg's own JavaScript sends numbers here too, through\n * `extractOpacity`, so this is the same translation and not an Android workaround.\n */\nconst NUMERIC: Record<string, number> = {\n opacity: 1,\n fillOpacity: 1,\n strokeOpacity: 1,\n strokeDashoffset: 0,\n strokeMiterlimit: 4,\n};\n\nfunction presentationValue(prop: string, value: string, context: SvgContext): unknown {\n if (prop === 'fill' || prop === 'stroke') return brushOf(value, context);\n if (prop === 'strokeLinecap') return LINECAP[value] ?? 0;\n if (prop === 'strokeLinejoin') return LINEJOIN[value] ?? 0;\n if (prop === 'fillRule' || prop === 'clipRule') return FILL_RULE[value] ?? 1;\n if (prop === 'strokeDasharray')\n return value\n .trim()\n .split(/[\\s,]+/)\n .filter(Boolean);\n return prop in NUMERIC ? numberFor(prop, value) : value;\n}\n\n/** The attribute as the float Android's view manager takes, or the default it applies. */\nfunction numberFor(prop: string, value: string): number {\n const number = Number.parseFloat(value);\n return Number.isNaN(number) ? NUMERIC[prop]! : number;\n}\n"]}
package/package.json CHANGED
@@ -1,12 +1,64 @@
1
1
  {
2
2
  "name": "@ng-native/icons",
3
- "version": "0.0.1",
4
- "description": "Reserved for ng-native. The first release follows.",
3
+ "version": "0.1.0",
4
+ "description": "@ng-icons rendered as native react-native-svg shapes in Angular Native.",
5
+ "keywords": [
6
+ "angular",
7
+ "react-native",
8
+ "angular-native",
9
+ "ios",
10
+ "android",
11
+ "fabric",
12
+ "native",
13
+ "icons",
14
+ "ng-icons",
15
+ "svg"
16
+ ],
17
+ "type": "module",
18
+ "main": "dist/index.js",
19
+ "dependencies": {
20
+ "@ng-native/fabric": "0.1.0"
21
+ },
22
+ "peerDependencies": {
23
+ "@angular/core": "^22.0.0",
24
+ "@ng-icons/core": ">=30",
25
+ "react-native-svg": ">=15"
26
+ },
27
+ "nx": {
28
+ "tags": [
29
+ "scope:icons",
30
+ "layer:angular"
31
+ ]
32
+ },
5
33
  "license": "MIT",
34
+ "homepage": "https://ng-native.com/packages/icons",
35
+ "bugs": {
36
+ "url": "https://github.com/ng-native/ng-native/issues"
37
+ },
6
38
  "repository": {
7
39
  "type": "git",
8
40
  "url": "git+https://github.com/ng-native/ng-native.git",
9
41
  "directory": "packages/icons"
10
42
  },
11
- "homepage": "https://ng-native.com/packages/icons"
43
+ "files": [
44
+ "dist"
45
+ ],
46
+ "devDependencies": {
47
+ "@angular/core": "22.2.0"
48
+ },
49
+ "exports": {
50
+ ".": {
51
+ "types": "./dist/index.d.ts",
52
+ "default": "./dist/index.js"
53
+ },
54
+ "./package.json": "./package.json"
55
+ },
56
+ "publishConfig": {
57
+ "access": "public"
58
+ },
59
+ "scripts": {
60
+ "build": "ngc -p tsconfig.build.json",
61
+ "typecheck": "tsc -p tsconfig.json --noEmit"
62
+ },
63
+ "types": "dist/index.d.ts"
12
64
  }